From 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:47:29 +0200 Subject: Adding upstream version 115.8.0esr. Signed-off-by: Daniel Baumann --- dom/svg/DOMSVGAngle.cpp | 117 + dom/svg/DOMSVGAngle.h | 66 + dom/svg/DOMSVGAnimatedAngle.cpp | 29 + dom/svg/DOMSVGAnimatedAngle.h | 47 + dom/svg/DOMSVGAnimatedBoolean.cpp | 21 + dom/svg/DOMSVGAnimatedBoolean.h | 44 + dom/svg/DOMSVGAnimatedEnumeration.cpp | 21 + dom/svg/DOMSVGAnimatedEnumeration.h | 39 + dom/svg/DOMSVGAnimatedInteger.cpp | 21 + dom/svg/DOMSVGAnimatedInteger.h | 39 + dom/svg/DOMSVGAnimatedLength.cpp | 31 + dom/svg/DOMSVGAnimatedLength.h | 46 + dom/svg/DOMSVGAnimatedLengthList.cpp | 126 + dom/svg/DOMSVGAnimatedLengthList.h | 204 + dom/svg/DOMSVGAnimatedNumber.cpp | 21 + dom/svg/DOMSVGAnimatedNumber.h | 39 + dom/svg/DOMSVGAnimatedNumberList.cpp | 127 + dom/svg/DOMSVGAnimatedNumberList.h | 132 + dom/svg/DOMSVGAnimatedString.cpp | 21 + dom/svg/DOMSVGAnimatedString.h | 49 + dom/svg/DOMSVGAnimatedTransformList.cpp | 119 + dom/svg/DOMSVGAnimatedTransformList.h | 128 + dom/svg/DOMSVGLength.cpp | 443 ++ dom/svg/DOMSVGLength.h | 212 + dom/svg/DOMSVGLengthList.cpp | 360 ++ dom/svg/DOMSVGLengthList.h | 209 + dom/svg/DOMSVGNumber.cpp | 141 + dom/svg/DOMSVGNumber.h | 172 + dom/svg/DOMSVGNumberList.cpp | 342 ++ dom/svg/DOMSVGNumberList.h | 194 + dom/svg/DOMSVGPathSeg.cpp | 314 ++ dom/svg/DOMSVGPathSeg.h | 655 +++ dom/svg/DOMSVGPathSegList.cpp | 535 +++ dom/svg/DOMSVGPathSegList.h | 265 ++ dom/svg/DOMSVGPoint.cpp | 227 + dom/svg/DOMSVGPoint.h | 187 + dom/svg/DOMSVGPointList.cpp | 414 ++ dom/svg/DOMSVGPointList.h | 260 ++ dom/svg/DOMSVGStringList.cpp | 206 + dom/svg/DOMSVGStringList.h | 116 + dom/svg/DOMSVGTransform.cpp | 306 ++ dom/svg/DOMSVGTransform.h | 180 + dom/svg/DOMSVGTransformList.cpp | 389 ++ dom/svg/DOMSVGTransformList.h | 198 + dom/svg/SVGAElement.cpp | 278 ++ dom/svg/SVGAElement.h | 110 + dom/svg/SVGAnimateElement.cpp | 37 + dom/svg/SVGAnimateElement.h | 42 + dom/svg/SVGAnimateMotionElement.cpp | 47 + dom/svg/SVGAnimateMotionElement.h | 54 + dom/svg/SVGAnimateTransformElement.cpp | 57 + dom/svg/SVGAnimateTransformElement.h | 48 + dom/svg/SVGAnimatedBoolean.cpp | 185 + dom/svg/SVGAnimatedBoolean.h | 83 + dom/svg/SVGAnimatedClass.cpp | 96 + dom/svg/SVGAnimatedClass.h | 71 + dom/svg/SVGAnimatedClassOrString.cpp | 33 + dom/svg/SVGAnimatedClassOrString.h | 40 + dom/svg/SVGAnimatedEnumeration.cpp | 203 + dom/svg/SVGAnimatedEnumeration.h | 120 + dom/svg/SVGAnimatedInteger.cpp | 168 + dom/svg/SVGAnimatedInteger.h | 110 + dom/svg/SVGAnimatedIntegerPair.cpp | 249 + dom/svg/SVGAnimatedIntegerPair.h | 121 + dom/svg/SVGAnimatedLength.cpp | 474 ++ dom/svg/SVGAnimatedLength.h | 221 + dom/svg/SVGAnimatedLengthList.cpp | 186 + dom/svg/SVGAnimatedLengthList.h | 124 + dom/svg/SVGAnimatedNumber.cpp | 190 + dom/svg/SVGAnimatedNumber.h | 113 + dom/svg/SVGAnimatedNumberList.cpp | 161 + dom/svg/SVGAnimatedNumberList.h | 125 + dom/svg/SVGAnimatedNumberPair.cpp | 241 + dom/svg/SVGAnimatedNumberPair.h | 125 + dom/svg/SVGAnimatedOrient.cpp | 515 ++ dom/svg/SVGAnimatedOrient.h | 153 + dom/svg/SVGAnimatedPathSegList.cpp | 201 + dom/svg/SVGAnimatedPathSegList.h | 124 + dom/svg/SVGAnimatedPointList.cpp | 183 + dom/svg/SVGAnimatedPointList.h | 117 + dom/svg/SVGAnimatedPreserveAspectRatio.cpp | 237 + dom/svg/SVGAnimatedPreserveAspectRatio.h | 139 + dom/svg/SVGAnimatedRect.cpp | 38 + dom/svg/SVGAnimatedRect.h | 52 + dom/svg/SVGAnimatedString.cpp | 97 + dom/svg/SVGAnimatedString.h | 101 + dom/svg/SVGAnimatedTransformList.cpp | 297 ++ dom/svg/SVGAnimatedTransformList.h | 155 + dom/svg/SVGAnimatedViewBox.cpp | 299 ++ dom/svg/SVGAnimatedViewBox.h | 122 + dom/svg/SVGAnimationElement.cpp | 367 ++ dom/svg/SVGAnimationElement.h | 119 + dom/svg/SVGAttrTearoffTable.h | 98 + dom/svg/SVGAttrValueWrapper.cpp | 97 + dom/svg/SVGAttrValueWrapper.h | 54 + dom/svg/SVGCircleElement.cpp | 174 + dom/svg/SVGCircleElement.h | 68 + dom/svg/SVGClipPathElement.cpp | 54 + dom/svg/SVGClipPathElement.h | 55 + dom/svg/SVGComponentTransferFunctionElement.h | 193 + dom/svg/SVGContentUtils.cpp | 859 ++++ dom/svg/SVGContentUtils.h | 349 ++ dom/svg/SVGDataParser.cpp | 39 + dom/svg/SVGDataParser.h | 42 + dom/svg/SVGDefsElement.cpp | 31 + dom/svg/SVGDefsElement.h | 37 + dom/svg/SVGDescElement.cpp | 31 + dom/svg/SVGDescElement.h | 36 + dom/svg/SVGDocument.cpp | 54 + dom/svg/SVGDocument.h | 40 + dom/svg/SVGElement.cpp | 2400 ++++++++++ dom/svg/SVGElement.h | 590 +++ dom/svg/SVGElementFactory.cpp | 105 + dom/svg/SVGElementFactory.h | 51 + dom/svg/SVGEllipseElement.cpp | 193 + dom/svg/SVGEllipseElement.h | 69 + dom/svg/SVGFEBlendElement.cpp | 114 + dom/svg/SVGFEBlendElement.h | 67 + dom/svg/SVGFEColorMatrixElement.cpp | 137 + dom/svg/SVGFEColorMatrixElement.h | 77 + dom/svg/SVGFEComponentTransferElement.cpp | 101 + dom/svg/SVGFEComponentTransferElement.h | 62 + dom/svg/SVGFECompositeElement.cpp | 147 + dom/svg/SVGFECompositeElement.h | 80 + dom/svg/SVGFEConvolveMatrixElement.cpp | 278 ++ dom/svg/SVGFEConvolveMatrixElement.h | 116 + dom/svg/SVGFEDiffuseLightingElement.cpp | 89 + dom/svg/SVGFEDiffuseLightingElement.h | 54 + dom/svg/SVGFEDisplacementMapElement.cpp | 139 + dom/svg/SVGFEDisplacementMapElement.h | 90 + dom/svg/SVGFEDistantLightElement.cpp | 67 + dom/svg/SVGFEDistantLightElement.h | 54 + dom/svg/SVGFEDropShadowElement.cpp | 138 + dom/svg/SVGFEDropShadowElement.h | 77 + dom/svg/SVGFEFloodElement.cpp | 72 + dom/svg/SVGFEFloodElement.h | 58 + dom/svg/SVGFEGaussianBlurElement.cpp | 116 + dom/svg/SVGFEGaussianBlurElement.h | 70 + dom/svg/SVGFEImageElement.cpp | 375 ++ dom/svg/SVGFEImageElement.h | 121 + dom/svg/SVGFEMergeElement.cpp | 61 + dom/svg/SVGFEMergeElement.h | 55 + dom/svg/SVGFEMergeNodeElement.cpp | 47 + dom/svg/SVGFEMergeNodeElement.h | 55 + dom/svg/SVGFEMorphologyElement.cpp | 140 + dom/svg/SVGFEMorphologyElement.h | 82 + dom/svg/SVGFEOffsetElement.cpp | 98 + dom/svg/SVGFEOffsetElement.h | 69 + dom/svg/SVGFEPointLightElement.cpp | 76 + dom/svg/SVGFEPointLightElement.h | 54 + dom/svg/SVGFESpecularLightingElement.cpp | 105 + dom/svg/SVGFESpecularLightingElement.h | 57 + dom/svg/SVGFESpotLightElement.cpp | 115 + dom/svg/SVGFESpotLightElement.h | 70 + dom/svg/SVGFETileElement.cpp | 74 + dom/svg/SVGFETileElement.h | 61 + dom/svg/SVGFETurbulenceElement.cpp | 194 + dom/svg/SVGFETurbulenceElement.h | 92 + dom/svg/SVGFilterElement.cpp | 117 + dom/svg/SVGFilterElement.h | 78 + dom/svg/SVGFilters.cpp | 450 ++ dom/svg/SVGFilters.h | 243 + dom/svg/SVGForeignObjectElement.cpp | 143 + dom/svg/SVGForeignObjectElement.h | 64 + dom/svg/SVGFragmentIdentifier.cpp | 182 + dom/svg/SVGFragmentIdentifier.h | 48 + dom/svg/SVGGElement.cpp | 30 + dom/svg/SVGGElement.h | 32 + dom/svg/SVGGeometryElement.cpp | 300 ++ dom/svg/SVGGeometryElement.h | 257 + dom/svg/SVGGeometryProperty.cpp | 91 + dom/svg/SVGGeometryProperty.h | 279 ++ dom/svg/SVGGradientElement.cpp | 219 + dom/svg/SVGGradientElement.h | 148 + dom/svg/SVGGraphicsElement.cpp | 186 + dom/svg/SVGGraphicsElement.h | 62 + dom/svg/SVGImageElement.cpp | 318 ++ dom/svg/SVGImageElement.h | 131 + dom/svg/SVGIntegerPairSMILType.cpp | 97 + dom/svg/SVGIntegerPairSMILType.h | 46 + dom/svg/SVGLength.cpp | 255 + dom/svg/SVGLength.h | 158 + dom/svg/SVGLengthList.cpp | 75 + dom/svg/SVGLengthList.h | 339 ++ dom/svg/SVGLengthListSMILType.cpp | 290 ++ dom/svg/SVGLengthListSMILType.h | 96 + dom/svg/SVGLineElement.cpp | 224 + dom/svg/SVGLineElement.h | 61 + dom/svg/SVGMPathElement.cpp | 240 + dom/svg/SVGMPathElement.h | 113 + dom/svg/SVGMarkerElement.cpp | 218 + dom/svg/SVGMarkerElement.h | 100 + dom/svg/SVGMaskElement.cpp | 103 + dom/svg/SVGMaskElement.h | 67 + dom/svg/SVGMatrix.cpp | 185 + dom/svg/SVGMatrix.h | 129 + dom/svg/SVGMetadataElement.cpp | 33 + dom/svg/SVGMetadataElement.h | 38 + dom/svg/SVGMotionSMILAnimationFunction.cpp | 417 ++ dom/svg/SVGMotionSMILAnimationFunction.h | 102 + dom/svg/SVGMotionSMILAttr.cpp | 47 + dom/svg/SVGMotionSMILAttr.h | 57 + dom/svg/SVGMotionSMILPathUtils.cpp | 137 + dom/svg/SVGMotionSMILPathUtils.h | 104 + dom/svg/SVGMotionSMILType.cpp | 459 ++ dom/svg/SVGMotionSMILType.h | 76 + dom/svg/SVGNumberList.cpp | 65 + dom/svg/SVGNumberList.h | 198 + dom/svg/SVGNumberListSMILType.cpp | 205 + dom/svg/SVGNumberListSMILType.h | 50 + dom/svg/SVGNumberPairSMILType.cpp | 98 + dom/svg/SVGNumberPairSMILType.h | 43 + dom/svg/SVGOrientSMILType.cpp | 143 + dom/svg/SVGOrientSMILType.h | 57 + dom/svg/SVGPathData.cpp | 1357 ++++++ dom/svg/SVGPathData.h | 312 ++ dom/svg/SVGPathDataParser.cpp | 464 ++ dom/svg/SVGPathDataParser.h | 74 + dom/svg/SVGPathElement.cpp | 381 ++ dom/svg/SVGPathElement.h | 133 + dom/svg/SVGPathSegListSMILType.cpp | 466 ++ dom/svg/SVGPathSegListSMILType.h | 53 + dom/svg/SVGPathSegUtils.cpp | 810 ++++ dom/svg/SVGPathSegUtils.h | 286 ++ dom/svg/SVGPatternElement.cpp | 157 + dom/svg/SVGPatternElement.h | 96 + dom/svg/SVGPoint.h | 81 + dom/svg/SVGPointList.cpp | 96 + dom/svg/SVGPointList.h | 207 + dom/svg/SVGPointListSMILType.cpp | 181 + dom/svg/SVGPointListSMILType.h | 50 + dom/svg/SVGPolyElement.cpp | 118 + dom/svg/SVGPolyElement.h | 58 + dom/svg/SVGPolygonElement.cpp | 77 + dom/svg/SVGPolygonElement.h | 39 + dom/svg/SVGPolylineElement.cpp | 52 + dom/svg/SVGPolylineElement.h | 38 + dom/svg/SVGPreserveAspectRatio.cpp | 145 + dom/svg/SVGPreserveAspectRatio.h | 115 + dom/svg/SVGRect.cpp | 150 + dom/svg/SVGRect.h | 87 + dom/svg/SVGRectElement.cpp | 280 ++ dom/svg/SVGRectElement.h | 71 + dom/svg/SVGSVGElement.cpp | 585 +++ dom/svg/SVGSVGElement.h | 284 ++ dom/svg/SVGScriptElement.cpp | 208 + dom/svg/SVGScriptElement.h | 85 + dom/svg/SVGSetElement.cpp | 37 + dom/svg/SVGSetElement.h | 42 + dom/svg/SVGStopElement.cpp | 47 + dom/svg/SVGStopElement.h | 44 + dom/svg/SVGStringList.cpp | 59 + dom/svg/SVGStringList.h | 137 + dom/svg/SVGStyleElement.cpp | 210 + dom/svg/SVGStyleElement.h | 93 + dom/svg/SVGSwitchElement.cpp | 139 + dom/svg/SVGSwitchElement.h | 63 + dom/svg/SVGSymbolElement.cpp | 37 + dom/svg/SVGSymbolElement.h | 38 + dom/svg/SVGTSpanElement.cpp | 40 + dom/svg/SVGTSpanElement.h | 45 + dom/svg/SVGTagList.h | 100 + dom/svg/SVGTests.cpp | 201 + dom/svg/SVGTests.h | 130 + dom/svg/SVGTextContentElement.cpp | 203 + dom/svg/SVGTextContentElement.h | 76 + dom/svg/SVGTextElement.cpp | 40 + dom/svg/SVGTextElement.h | 45 + dom/svg/SVGTextPathElement.cpp | 125 + dom/svg/SVGTextPathElement.h | 85 + dom/svg/SVGTextPositioningElement.cpp | 65 + dom/svg/SVGTextPositioningElement.h | 51 + dom/svg/SVGTitleElement.cpp | 89 + dom/svg/SVGTitleElement.h | 58 + dom/svg/SVGTransform.cpp | 212 + dom/svg/SVGTransform.h | 153 + dom/svg/SVGTransformList.cpp | 69 + dom/svg/SVGTransformList.h | 131 + dom/svg/SVGTransformListParser.cpp | 251 + dom/svg/SVGTransformListParser.h | 54 + dom/svg/SVGTransformListSMILType.cpp | 343 ++ dom/svg/SVGTransformListSMILType.h | 121 + dom/svg/SVGTransformableElement.cpp | 151 + dom/svg/SVGTransformableElement.h | 77 + dom/svg/SVGUseElement.cpp | 669 +++ dom/svg/SVGUseElement.h | 178 + dom/svg/SVGViewBoxSMILType.cpp | 125 + dom/svg/SVGViewBoxSMILType.h | 43 + dom/svg/SVGViewElement.cpp | 79 + dom/svg/SVGViewElement.h | 72 + dom/svg/SVGViewportElement.cpp | 346 ++ dom/svg/SVGViewportElement.h | 201 + dom/svg/crashtests/1035248-1.svg | 18 + dom/svg/crashtests/1035248-2.svg | 16 + dom/svg/crashtests/1244898-1.xhtml | 14 + dom/svg/crashtests/1250725.html | 16 + dom/svg/crashtests/1267272-1.svg | 7 + dom/svg/crashtests/1282985-1.svg | 24 + dom/svg/crashtests/1322286.html | 3 + dom/svg/crashtests/1329093-1.html | 9 + dom/svg/crashtests/1329093-2.html | 28 + dom/svg/crashtests/1329849-1.svg | 13 + dom/svg/crashtests/1329849-2.svg | 13 + dom/svg/crashtests/1329849-3.svg | 13 + dom/svg/crashtests/1329849-4.svg | 13 + dom/svg/crashtests/1329849-5.svg | 13 + dom/svg/crashtests/1329849-6.svg | 13 + dom/svg/crashtests/1343147.svg | 13 + dom/svg/crashtests/1347617-1.svg | 10 + dom/svg/crashtests/1347617-2.svg | 10 + dom/svg/crashtests/1347617-3.svg | 10 + dom/svg/crashtests/1402798.html | 11 + dom/svg/crashtests/1419250-1.html | 1 + dom/svg/crashtests/1420492.html | 3 + dom/svg/crashtests/1477853.html | 10 + dom/svg/crashtests/1486488.html | 11 + dom/svg/crashtests/1493447.html | 12 + dom/svg/crashtests/1507961-1.html | 4066 ++++++++++++++++ dom/svg/crashtests/1513603.html | 20 + dom/svg/crashtests/1531578-1.html | 17 + dom/svg/crashtests/1555795.html | 22 + dom/svg/crashtests/1560179.html | 5 + dom/svg/crashtests/1572904.html | 15 + dom/svg/crashtests/1683907.html | 20 + dom/svg/crashtests/1715387.html | 45 + dom/svg/crashtests/307322-1.svg | 8 + dom/svg/crashtests/327705-1.svg | 19 + dom/svg/crashtests/336994-1.html | 12 + dom/svg/crashtests/344888-1.svg | 4 + dom/svg/crashtests/345445-1.svg | 23 + dom/svg/crashtests/360836-1.svg | 14 + dom/svg/crashtests/369051-1.svg | 9 + dom/svg/crashtests/369291-2.svg | 19 + dom/svg/crashtests/369568-1.svg | 20 + dom/svg/crashtests/374882-1.svg | 14 + dom/svg/crashtests/380101-1.svg | 30 + dom/svg/crashtests/381777-1.svg | 96 + dom/svg/crashtests/383685-1.svg | 16 + dom/svg/crashtests/385096.html | 14 + dom/svg/crashtests/385554-1.html | 6 + dom/svg/crashtests/385554-2.xhtml | 5 + dom/svg/crashtests/388712-1.svg | 12 + dom/svg/crashtests/395616-1.html | 13 + dom/svg/crashtests/396618-1.html | 14 + dom/svg/crashtests/397017-1.html | 11 + dom/svg/crashtests/397551-1.svg | 7 + dom/svg/crashtests/397704-1.svg | 1 + dom/svg/crashtests/398926-both-different.svg | 8 + dom/svg/crashtests/398926-both-same.svg | 7 + dom/svg/crashtests/398926-fill.svg | 7 + dom/svg/crashtests/398926-stroke.svg | 7 + dom/svg/crashtests/405639-1.svg | 19 + dom/svg/crashtests/406361-1.html | 13 + dom/svg/crashtests/409811-1.html | 15 + dom/svg/crashtests/410659-1.svg | 19 + dom/svg/crashtests/410659-2.svg | 19 + dom/svg/crashtests/410659-3.svg | 19 + dom/svg/crashtests/413174-1.svg | 25 + dom/svg/crashtests/414188-1.svg | 10 + dom/svg/crashtests/427325-1.svg | 11 + dom/svg/crashtests/428228-1.svg | 17 + dom/svg/crashtests/428841-1.svg | 13 + dom/svg/crashtests/436418-mpathRoot-1.svg | 4 + dom/svg/crashtests/448244-1.svg | 9 + dom/svg/crashtests/466576-1.xhtml | 22 + dom/svg/crashtests/499879-1.svg | 1 + dom/svg/crashtests/535691-1.svg | 1 + dom/svg/crashtests/539167-1.svg | 6 + dom/svg/crashtests/573316-1.svg | 3 + dom/svg/crashtests/579356-1.svg | 9 + dom/svg/crashtests/579356-2.svg | 13 + dom/svg/crashtests/595608-1.svg | 1 + dom/svg/crashtests/601251-1.html | 8 + dom/svg/crashtests/601406-1.svg | 12 + dom/svg/crashtests/603145-1.svg | 11 + dom/svg/crashtests/613899-1.svg | 9 + dom/svg/crashtests/613899-2.svg | 7 + dom/svg/crashtests/719779-1.svg | 19 + dom/svg/crashtests/723441-1.html | 22 + dom/svg/crashtests/723441-resource.svg | 4922 ++++++++++++++++++++ dom/svg/crashtests/751515-1.svg | 9 + dom/svg/crashtests/761507-1.svg | 18 + dom/svg/crashtests/821955-1.html | 5 + dom/svg/crashtests/831561.html | 10 + dom/svg/crashtests/842463-1.html | 16 + dom/svg/crashtests/847138-1.svg | 8 + dom/svg/crashtests/864509.svg | 7 + dom/svg/crashtests/898915-1.svg | 5 + dom/svg/crashtests/crashtests.list | 99 + dom/svg/crashtests/invalid-image.svg | 1 + .../crashtests/long-clipPath-reference-chain.svg | 53 + dom/svg/crashtests/test_nested_svg.html | 63 + dom/svg/crashtests/zero-size-image.svg | 6 + dom/svg/moz.build | 271 ++ dom/svg/test/MutationEventChecker.js | 278 ++ dom/svg/test/a_href_destination.svg | 3 + dom/svg/test/a_href_helper_01.svg | 5 + dom/svg/test/a_href_helper_02_03.svg | 5 + dom/svg/test/a_href_helper_04.svg | 6 + dom/svg/test/a_href_helper_05.svg | 5 + dom/svg/test/a_href_helper_06.svg | 5 + dom/svg/test/a_href_helper_07.svg | 6 + dom/svg/test/animated-svg-image-helper.html | 3 + dom/svg/test/animated-svg-image-helper.svg | 3 + dom/svg/test/bbox-helper.svg | 42 + dom/svg/test/bounds-helper.svg | 86 + dom/svg/test/dataTypes-helper.svg | 20 + dom/svg/test/fragments-helper.svg | 4 + dom/svg/test/getBBox-method-helper.svg | 304 ++ dom/svg/test/getCTM-helper.svg | 47 + dom/svg/test/getSubStringLength-helper.svg | 7 + dom/svg/test/matrixUtils.js | 78 + dom/svg/test/mochitest.ini | 113 + dom/svg/test/object-delayed-intrinsic-size.sjs | 24 + dom/svg/test/pointer-events.js | 328 ++ dom/svg/test/scientific-helper.svg | 5 + dom/svg/test/selectSubString-helper.svg | 7 + dom/svg/test/switch-helper.svg | 12 + dom/svg/test/tearoff_with_cc_helper.html | 36 + dom/svg/test/test_SVGLengthList-2.xhtml | 64 + dom/svg/test/test_SVGLengthList.xhtml | 158 + dom/svg/test/test_SVGMatrix.xhtml | 180 + dom/svg/test/test_SVGNumberList.xhtml | 74 + dom/svg/test/test_SVGPointList.xhtml | 129 + dom/svg/test/test_SVGStringList.xhtml | 118 + dom/svg/test/test_SVGStyleElement.xhtml | 33 + dom/svg/test/test_SVGTransformList.xhtml | 461 ++ dom/svg/test/test_SVGTransformListAddition.xhtml | 185 + dom/svg/test/test_SVG_namespace_ids.html | 113 + dom/svg/test/test_SVGxxxList.xhtml | 1372 ++++++ dom/svg/test/test_SVGxxxListIndexing.xhtml | 93 + dom/svg/test/test_a_href_01.xhtml | 96 + dom/svg/test/test_a_href_02.xhtml | 37 + dom/svg/test/test_animLengthObjectIdentity.xhtml | 86 + dom/svg/test/test_animLengthReadonly.xhtml | 219 + dom/svg/test/test_animLengthUnits.xhtml | 125 + dom/svg/test/test_bbox-changes.xhtml | 77 + dom/svg/test/test_bbox-with-invalid-viewBox.xhtml | 38 + dom/svg/test/test_bbox.xhtml | 91 + dom/svg/test/test_bounds.html | 317 ++ dom/svg/test/test_bug1426594.html | 34 + dom/svg/test/test_bug872812.html | 29 + dom/svg/test/test_dataTypes.html | 377 ++ dom/svg/test/test_dataTypesModEvents.html | 257 + dom/svg/test/test_fragments.html | 92 + dom/svg/test/test_getBBox-method.html | 248 + dom/svg/test/test_getCTM.html | 124 + dom/svg/test/test_getElementById.xhtml | 65 + ...est_getPathSegListAtLength_with_d_property.html | 55 + dom/svg/test/test_getSubStringLength.xhtml | 91 + dom/svg/test/test_getTotalLength.xhtml | 57 + dom/svg/test/test_hit-testing-and-viewbox.xhtml | 81 + dom/svg/test/test_lang.xhtml | 90 + dom/svg/test/test_length.xhtml | 58 + dom/svg/test/test_lengthParsing.html | 82 + dom/svg/test/test_markerOrient.xhtml | 110 + dom/svg/test/test_non-scaling-stroke.html | 52 + dom/svg/test/test_nonAnimStrings.xhtml | 78 + .../test/test_object-delayed-intrinsic-size.html | 39 + dom/svg/test/test_onerror.xhtml | 35 + dom/svg/test/test_onload.xhtml | 35 + dom/svg/test/test_onload2.xhtml | 48 + dom/svg/test/test_pairParsing.html | 43 + dom/svg/test/test_pathAnimInterpolation.xhtml | 341 ++ dom/svg/test/test_pointAtLength.xhtml | 49 + dom/svg/test/test_pointer-events-1a.xhtml | 27 + dom/svg/test/test_pointer-events-1b.xhtml | 27 + dom/svg/test/test_pointer-events-2.xhtml | 71 + dom/svg/test/test_pointer-events-3.xhtml | 54 + dom/svg/test/test_pointer-events-4.xhtml | 109 + dom/svg/test/test_pointer-events-6.xhtml | 69 + dom/svg/test/test_pointer-events-7.xhtml | 65 + dom/svg/test/test_scientific.html | 82 + dom/svg/test/test_selectSubString.xhtml | 74 + dom/svg/test/test_stroke-hit-testing.xhtml | 66 + dom/svg/test/test_stroke-linecap-hit-testing.xhtml | 45 + dom/svg/test/test_style_sheet.html | 27 + dom/svg/test/test_switch.xhtml | 99 + dom/svg/test/test_tabindex.html | 103 + dom/svg/test/test_tearoff_with_cc.html | 48 + dom/svg/test/test_text.html | 189 + dom/svg/test/test_text_2.html | 63 + dom/svg/test/test_text_dirty.html | 47 + dom/svg/test/test_text_lengthAdjust.html | 106 + dom/svg/test/test_text_scaled.html | 135 + dom/svg/test/test_text_selection.html | 139 + dom/svg/test/test_text_update.html | 31 + dom/svg/test/test_transform.xhtml | 190 + dom/svg/test/test_transformParsing.html | 103 + dom/svg/test/test_use_with_hsts.html | 132 + dom/svg/test/test_valueAsString.xhtml | 64 + dom/svg/test/test_valueLeaks.xhtml | 84 + dom/svg/test/test_viewBox.html | 86 + dom/svg/test/test_viewport.html | 59 + dom/svg/test/text-helper-scaled.svg | 8 + dom/svg/test/text-helper-selection.svg | 23 + dom/svg/test/text-helper.svg | 19 + dom/svg/test/use-with-hsts-helper.html | 30 + dom/svg/test/use-with-hsts-helper.html^headers^ | 2 + dom/svg/test/viewport-helper.svg | 26 + 500 files changed, 68146 insertions(+) create mode 100644 dom/svg/DOMSVGAngle.cpp create mode 100644 dom/svg/DOMSVGAngle.h create mode 100644 dom/svg/DOMSVGAnimatedAngle.cpp create mode 100644 dom/svg/DOMSVGAnimatedAngle.h create mode 100644 dom/svg/DOMSVGAnimatedBoolean.cpp create mode 100644 dom/svg/DOMSVGAnimatedBoolean.h create mode 100644 dom/svg/DOMSVGAnimatedEnumeration.cpp create mode 100644 dom/svg/DOMSVGAnimatedEnumeration.h create mode 100644 dom/svg/DOMSVGAnimatedInteger.cpp create mode 100644 dom/svg/DOMSVGAnimatedInteger.h create mode 100644 dom/svg/DOMSVGAnimatedLength.cpp create mode 100644 dom/svg/DOMSVGAnimatedLength.h create mode 100644 dom/svg/DOMSVGAnimatedLengthList.cpp create mode 100644 dom/svg/DOMSVGAnimatedLengthList.h create mode 100644 dom/svg/DOMSVGAnimatedNumber.cpp create mode 100644 dom/svg/DOMSVGAnimatedNumber.h create mode 100644 dom/svg/DOMSVGAnimatedNumberList.cpp create mode 100644 dom/svg/DOMSVGAnimatedNumberList.h create mode 100644 dom/svg/DOMSVGAnimatedString.cpp create mode 100644 dom/svg/DOMSVGAnimatedString.h create mode 100644 dom/svg/DOMSVGAnimatedTransformList.cpp create mode 100644 dom/svg/DOMSVGAnimatedTransformList.h create mode 100644 dom/svg/DOMSVGLength.cpp create mode 100644 dom/svg/DOMSVGLength.h create mode 100644 dom/svg/DOMSVGLengthList.cpp create mode 100644 dom/svg/DOMSVGLengthList.h create mode 100644 dom/svg/DOMSVGNumber.cpp create mode 100644 dom/svg/DOMSVGNumber.h create mode 100644 dom/svg/DOMSVGNumberList.cpp create mode 100644 dom/svg/DOMSVGNumberList.h create mode 100644 dom/svg/DOMSVGPathSeg.cpp create mode 100644 dom/svg/DOMSVGPathSeg.h create mode 100644 dom/svg/DOMSVGPathSegList.cpp create mode 100644 dom/svg/DOMSVGPathSegList.h create mode 100644 dom/svg/DOMSVGPoint.cpp create mode 100644 dom/svg/DOMSVGPoint.h create mode 100644 dom/svg/DOMSVGPointList.cpp create mode 100644 dom/svg/DOMSVGPointList.h create mode 100644 dom/svg/DOMSVGStringList.cpp create mode 100644 dom/svg/DOMSVGStringList.h create mode 100644 dom/svg/DOMSVGTransform.cpp create mode 100644 dom/svg/DOMSVGTransform.h create mode 100644 dom/svg/DOMSVGTransformList.cpp create mode 100644 dom/svg/DOMSVGTransformList.h create mode 100644 dom/svg/SVGAElement.cpp create mode 100644 dom/svg/SVGAElement.h create mode 100644 dom/svg/SVGAnimateElement.cpp create mode 100644 dom/svg/SVGAnimateElement.h create mode 100644 dom/svg/SVGAnimateMotionElement.cpp create mode 100644 dom/svg/SVGAnimateMotionElement.h create mode 100644 dom/svg/SVGAnimateTransformElement.cpp create mode 100644 dom/svg/SVGAnimateTransformElement.h create mode 100644 dom/svg/SVGAnimatedBoolean.cpp create mode 100644 dom/svg/SVGAnimatedBoolean.h create mode 100644 dom/svg/SVGAnimatedClass.cpp create mode 100644 dom/svg/SVGAnimatedClass.h create mode 100644 dom/svg/SVGAnimatedClassOrString.cpp create mode 100644 dom/svg/SVGAnimatedClassOrString.h create mode 100644 dom/svg/SVGAnimatedEnumeration.cpp create mode 100644 dom/svg/SVGAnimatedEnumeration.h create mode 100644 dom/svg/SVGAnimatedInteger.cpp create mode 100644 dom/svg/SVGAnimatedInteger.h create mode 100644 dom/svg/SVGAnimatedIntegerPair.cpp create mode 100644 dom/svg/SVGAnimatedIntegerPair.h create mode 100644 dom/svg/SVGAnimatedLength.cpp create mode 100644 dom/svg/SVGAnimatedLength.h create mode 100644 dom/svg/SVGAnimatedLengthList.cpp create mode 100644 dom/svg/SVGAnimatedLengthList.h create mode 100644 dom/svg/SVGAnimatedNumber.cpp create mode 100644 dom/svg/SVGAnimatedNumber.h create mode 100644 dom/svg/SVGAnimatedNumberList.cpp create mode 100644 dom/svg/SVGAnimatedNumberList.h create mode 100644 dom/svg/SVGAnimatedNumberPair.cpp create mode 100644 dom/svg/SVGAnimatedNumberPair.h create mode 100644 dom/svg/SVGAnimatedOrient.cpp create mode 100644 dom/svg/SVGAnimatedOrient.h create mode 100644 dom/svg/SVGAnimatedPathSegList.cpp create mode 100644 dom/svg/SVGAnimatedPathSegList.h create mode 100644 dom/svg/SVGAnimatedPointList.cpp create mode 100644 dom/svg/SVGAnimatedPointList.h create mode 100644 dom/svg/SVGAnimatedPreserveAspectRatio.cpp create mode 100644 dom/svg/SVGAnimatedPreserveAspectRatio.h create mode 100644 dom/svg/SVGAnimatedRect.cpp create mode 100644 dom/svg/SVGAnimatedRect.h create mode 100644 dom/svg/SVGAnimatedString.cpp create mode 100644 dom/svg/SVGAnimatedString.h create mode 100644 dom/svg/SVGAnimatedTransformList.cpp create mode 100644 dom/svg/SVGAnimatedTransformList.h create mode 100644 dom/svg/SVGAnimatedViewBox.cpp create mode 100644 dom/svg/SVGAnimatedViewBox.h create mode 100644 dom/svg/SVGAnimationElement.cpp create mode 100644 dom/svg/SVGAnimationElement.h create mode 100644 dom/svg/SVGAttrTearoffTable.h create mode 100644 dom/svg/SVGAttrValueWrapper.cpp create mode 100644 dom/svg/SVGAttrValueWrapper.h create mode 100644 dom/svg/SVGCircleElement.cpp create mode 100644 dom/svg/SVGCircleElement.h create mode 100644 dom/svg/SVGClipPathElement.cpp create mode 100644 dom/svg/SVGClipPathElement.h create mode 100644 dom/svg/SVGComponentTransferFunctionElement.h create mode 100644 dom/svg/SVGContentUtils.cpp create mode 100644 dom/svg/SVGContentUtils.h create mode 100644 dom/svg/SVGDataParser.cpp create mode 100644 dom/svg/SVGDataParser.h create mode 100644 dom/svg/SVGDefsElement.cpp create mode 100644 dom/svg/SVGDefsElement.h create mode 100644 dom/svg/SVGDescElement.cpp create mode 100644 dom/svg/SVGDescElement.h create mode 100644 dom/svg/SVGDocument.cpp create mode 100644 dom/svg/SVGDocument.h create mode 100644 dom/svg/SVGElement.cpp create mode 100644 dom/svg/SVGElement.h create mode 100644 dom/svg/SVGElementFactory.cpp create mode 100644 dom/svg/SVGElementFactory.h create mode 100644 dom/svg/SVGEllipseElement.cpp create mode 100644 dom/svg/SVGEllipseElement.h create mode 100644 dom/svg/SVGFEBlendElement.cpp create mode 100644 dom/svg/SVGFEBlendElement.h create mode 100644 dom/svg/SVGFEColorMatrixElement.cpp create mode 100644 dom/svg/SVGFEColorMatrixElement.h create mode 100644 dom/svg/SVGFEComponentTransferElement.cpp create mode 100644 dom/svg/SVGFEComponentTransferElement.h create mode 100644 dom/svg/SVGFECompositeElement.cpp create mode 100644 dom/svg/SVGFECompositeElement.h create mode 100644 dom/svg/SVGFEConvolveMatrixElement.cpp create mode 100644 dom/svg/SVGFEConvolveMatrixElement.h create mode 100644 dom/svg/SVGFEDiffuseLightingElement.cpp create mode 100644 dom/svg/SVGFEDiffuseLightingElement.h create mode 100644 dom/svg/SVGFEDisplacementMapElement.cpp create mode 100644 dom/svg/SVGFEDisplacementMapElement.h create mode 100644 dom/svg/SVGFEDistantLightElement.cpp create mode 100644 dom/svg/SVGFEDistantLightElement.h create mode 100644 dom/svg/SVGFEDropShadowElement.cpp create mode 100644 dom/svg/SVGFEDropShadowElement.h create mode 100644 dom/svg/SVGFEFloodElement.cpp create mode 100644 dom/svg/SVGFEFloodElement.h create mode 100644 dom/svg/SVGFEGaussianBlurElement.cpp create mode 100644 dom/svg/SVGFEGaussianBlurElement.h create mode 100644 dom/svg/SVGFEImageElement.cpp create mode 100644 dom/svg/SVGFEImageElement.h create mode 100644 dom/svg/SVGFEMergeElement.cpp create mode 100644 dom/svg/SVGFEMergeElement.h create mode 100644 dom/svg/SVGFEMergeNodeElement.cpp create mode 100644 dom/svg/SVGFEMergeNodeElement.h create mode 100644 dom/svg/SVGFEMorphologyElement.cpp create mode 100644 dom/svg/SVGFEMorphologyElement.h create mode 100644 dom/svg/SVGFEOffsetElement.cpp create mode 100644 dom/svg/SVGFEOffsetElement.h create mode 100644 dom/svg/SVGFEPointLightElement.cpp create mode 100644 dom/svg/SVGFEPointLightElement.h create mode 100644 dom/svg/SVGFESpecularLightingElement.cpp create mode 100644 dom/svg/SVGFESpecularLightingElement.h create mode 100644 dom/svg/SVGFESpotLightElement.cpp create mode 100644 dom/svg/SVGFESpotLightElement.h create mode 100644 dom/svg/SVGFETileElement.cpp create mode 100644 dom/svg/SVGFETileElement.h create mode 100644 dom/svg/SVGFETurbulenceElement.cpp create mode 100644 dom/svg/SVGFETurbulenceElement.h create mode 100644 dom/svg/SVGFilterElement.cpp create mode 100644 dom/svg/SVGFilterElement.h create mode 100644 dom/svg/SVGFilters.cpp create mode 100644 dom/svg/SVGFilters.h create mode 100644 dom/svg/SVGForeignObjectElement.cpp create mode 100644 dom/svg/SVGForeignObjectElement.h create mode 100644 dom/svg/SVGFragmentIdentifier.cpp create mode 100644 dom/svg/SVGFragmentIdentifier.h create mode 100644 dom/svg/SVGGElement.cpp create mode 100644 dom/svg/SVGGElement.h create mode 100644 dom/svg/SVGGeometryElement.cpp create mode 100644 dom/svg/SVGGeometryElement.h create mode 100644 dom/svg/SVGGeometryProperty.cpp create mode 100644 dom/svg/SVGGeometryProperty.h create mode 100644 dom/svg/SVGGradientElement.cpp create mode 100644 dom/svg/SVGGradientElement.h create mode 100644 dom/svg/SVGGraphicsElement.cpp create mode 100644 dom/svg/SVGGraphicsElement.h create mode 100644 dom/svg/SVGImageElement.cpp create mode 100644 dom/svg/SVGImageElement.h create mode 100644 dom/svg/SVGIntegerPairSMILType.cpp create mode 100644 dom/svg/SVGIntegerPairSMILType.h create mode 100644 dom/svg/SVGLength.cpp create mode 100644 dom/svg/SVGLength.h create mode 100644 dom/svg/SVGLengthList.cpp create mode 100644 dom/svg/SVGLengthList.h create mode 100644 dom/svg/SVGLengthListSMILType.cpp create mode 100644 dom/svg/SVGLengthListSMILType.h create mode 100644 dom/svg/SVGLineElement.cpp create mode 100644 dom/svg/SVGLineElement.h create mode 100644 dom/svg/SVGMPathElement.cpp create mode 100644 dom/svg/SVGMPathElement.h create mode 100644 dom/svg/SVGMarkerElement.cpp create mode 100644 dom/svg/SVGMarkerElement.h create mode 100644 dom/svg/SVGMaskElement.cpp create mode 100644 dom/svg/SVGMaskElement.h create mode 100644 dom/svg/SVGMatrix.cpp create mode 100644 dom/svg/SVGMatrix.h create mode 100644 dom/svg/SVGMetadataElement.cpp create mode 100644 dom/svg/SVGMetadataElement.h create mode 100644 dom/svg/SVGMotionSMILAnimationFunction.cpp create mode 100644 dom/svg/SVGMotionSMILAnimationFunction.h create mode 100644 dom/svg/SVGMotionSMILAttr.cpp create mode 100644 dom/svg/SVGMotionSMILAttr.h create mode 100644 dom/svg/SVGMotionSMILPathUtils.cpp create mode 100644 dom/svg/SVGMotionSMILPathUtils.h create mode 100644 dom/svg/SVGMotionSMILType.cpp create mode 100644 dom/svg/SVGMotionSMILType.h create mode 100644 dom/svg/SVGNumberList.cpp create mode 100644 dom/svg/SVGNumberList.h create mode 100644 dom/svg/SVGNumberListSMILType.cpp create mode 100644 dom/svg/SVGNumberListSMILType.h create mode 100644 dom/svg/SVGNumberPairSMILType.cpp create mode 100644 dom/svg/SVGNumberPairSMILType.h create mode 100644 dom/svg/SVGOrientSMILType.cpp create mode 100644 dom/svg/SVGOrientSMILType.h create mode 100644 dom/svg/SVGPathData.cpp create mode 100644 dom/svg/SVGPathData.h create mode 100644 dom/svg/SVGPathDataParser.cpp create mode 100644 dom/svg/SVGPathDataParser.h create mode 100644 dom/svg/SVGPathElement.cpp create mode 100644 dom/svg/SVGPathElement.h create mode 100644 dom/svg/SVGPathSegListSMILType.cpp create mode 100644 dom/svg/SVGPathSegListSMILType.h create mode 100644 dom/svg/SVGPathSegUtils.cpp create mode 100644 dom/svg/SVGPathSegUtils.h create mode 100644 dom/svg/SVGPatternElement.cpp create mode 100644 dom/svg/SVGPatternElement.h create mode 100644 dom/svg/SVGPoint.h create mode 100644 dom/svg/SVGPointList.cpp create mode 100644 dom/svg/SVGPointList.h create mode 100644 dom/svg/SVGPointListSMILType.cpp create mode 100644 dom/svg/SVGPointListSMILType.h create mode 100644 dom/svg/SVGPolyElement.cpp create mode 100644 dom/svg/SVGPolyElement.h create mode 100644 dom/svg/SVGPolygonElement.cpp create mode 100644 dom/svg/SVGPolygonElement.h create mode 100644 dom/svg/SVGPolylineElement.cpp create mode 100644 dom/svg/SVGPolylineElement.h create mode 100644 dom/svg/SVGPreserveAspectRatio.cpp create mode 100644 dom/svg/SVGPreserveAspectRatio.h create mode 100644 dom/svg/SVGRect.cpp create mode 100644 dom/svg/SVGRect.h create mode 100644 dom/svg/SVGRectElement.cpp create mode 100644 dom/svg/SVGRectElement.h create mode 100644 dom/svg/SVGSVGElement.cpp create mode 100644 dom/svg/SVGSVGElement.h create mode 100644 dom/svg/SVGScriptElement.cpp create mode 100644 dom/svg/SVGScriptElement.h create mode 100644 dom/svg/SVGSetElement.cpp create mode 100644 dom/svg/SVGSetElement.h create mode 100644 dom/svg/SVGStopElement.cpp create mode 100644 dom/svg/SVGStopElement.h create mode 100644 dom/svg/SVGStringList.cpp create mode 100644 dom/svg/SVGStringList.h create mode 100644 dom/svg/SVGStyleElement.cpp create mode 100644 dom/svg/SVGStyleElement.h create mode 100644 dom/svg/SVGSwitchElement.cpp create mode 100644 dom/svg/SVGSwitchElement.h create mode 100644 dom/svg/SVGSymbolElement.cpp create mode 100644 dom/svg/SVGSymbolElement.h create mode 100644 dom/svg/SVGTSpanElement.cpp create mode 100644 dom/svg/SVGTSpanElement.h create mode 100644 dom/svg/SVGTagList.h create mode 100644 dom/svg/SVGTests.cpp create mode 100644 dom/svg/SVGTests.h create mode 100644 dom/svg/SVGTextContentElement.cpp create mode 100644 dom/svg/SVGTextContentElement.h create mode 100644 dom/svg/SVGTextElement.cpp create mode 100644 dom/svg/SVGTextElement.h create mode 100644 dom/svg/SVGTextPathElement.cpp create mode 100644 dom/svg/SVGTextPathElement.h create mode 100644 dom/svg/SVGTextPositioningElement.cpp create mode 100644 dom/svg/SVGTextPositioningElement.h create mode 100644 dom/svg/SVGTitleElement.cpp create mode 100644 dom/svg/SVGTitleElement.h create mode 100644 dom/svg/SVGTransform.cpp create mode 100644 dom/svg/SVGTransform.h create mode 100644 dom/svg/SVGTransformList.cpp create mode 100644 dom/svg/SVGTransformList.h create mode 100644 dom/svg/SVGTransformListParser.cpp create mode 100644 dom/svg/SVGTransformListParser.h create mode 100644 dom/svg/SVGTransformListSMILType.cpp create mode 100644 dom/svg/SVGTransformListSMILType.h create mode 100644 dom/svg/SVGTransformableElement.cpp create mode 100644 dom/svg/SVGTransformableElement.h create mode 100644 dom/svg/SVGUseElement.cpp create mode 100644 dom/svg/SVGUseElement.h create mode 100644 dom/svg/SVGViewBoxSMILType.cpp create mode 100644 dom/svg/SVGViewBoxSMILType.h create mode 100644 dom/svg/SVGViewElement.cpp create mode 100644 dom/svg/SVGViewElement.h create mode 100644 dom/svg/SVGViewportElement.cpp create mode 100644 dom/svg/SVGViewportElement.h create mode 100644 dom/svg/crashtests/1035248-1.svg create mode 100644 dom/svg/crashtests/1035248-2.svg create mode 100644 dom/svg/crashtests/1244898-1.xhtml create mode 100644 dom/svg/crashtests/1250725.html create mode 100644 dom/svg/crashtests/1267272-1.svg create mode 100644 dom/svg/crashtests/1282985-1.svg create mode 100644 dom/svg/crashtests/1322286.html create mode 100644 dom/svg/crashtests/1329093-1.html create mode 100644 dom/svg/crashtests/1329093-2.html create mode 100644 dom/svg/crashtests/1329849-1.svg create mode 100644 dom/svg/crashtests/1329849-2.svg create mode 100644 dom/svg/crashtests/1329849-3.svg create mode 100644 dom/svg/crashtests/1329849-4.svg create mode 100644 dom/svg/crashtests/1329849-5.svg create mode 100644 dom/svg/crashtests/1329849-6.svg create mode 100644 dom/svg/crashtests/1343147.svg create mode 100644 dom/svg/crashtests/1347617-1.svg create mode 100644 dom/svg/crashtests/1347617-2.svg create mode 100644 dom/svg/crashtests/1347617-3.svg create mode 100644 dom/svg/crashtests/1402798.html create mode 100644 dom/svg/crashtests/1419250-1.html create mode 100644 dom/svg/crashtests/1420492.html create mode 100644 dom/svg/crashtests/1477853.html create mode 100644 dom/svg/crashtests/1486488.html create mode 100644 dom/svg/crashtests/1493447.html create mode 100644 dom/svg/crashtests/1507961-1.html create mode 100644 dom/svg/crashtests/1513603.html create mode 100644 dom/svg/crashtests/1531578-1.html create mode 100644 dom/svg/crashtests/1555795.html create mode 100644 dom/svg/crashtests/1560179.html create mode 100644 dom/svg/crashtests/1572904.html create mode 100644 dom/svg/crashtests/1683907.html create mode 100644 dom/svg/crashtests/1715387.html create mode 100644 dom/svg/crashtests/307322-1.svg create mode 100644 dom/svg/crashtests/327705-1.svg create mode 100644 dom/svg/crashtests/336994-1.html create mode 100644 dom/svg/crashtests/344888-1.svg create mode 100644 dom/svg/crashtests/345445-1.svg create mode 100644 dom/svg/crashtests/360836-1.svg create mode 100644 dom/svg/crashtests/369051-1.svg create mode 100644 dom/svg/crashtests/369291-2.svg create mode 100644 dom/svg/crashtests/369568-1.svg create mode 100644 dom/svg/crashtests/374882-1.svg create mode 100644 dom/svg/crashtests/380101-1.svg create mode 100644 dom/svg/crashtests/381777-1.svg create mode 100644 dom/svg/crashtests/383685-1.svg create mode 100644 dom/svg/crashtests/385096.html create mode 100644 dom/svg/crashtests/385554-1.html create mode 100644 dom/svg/crashtests/385554-2.xhtml create mode 100644 dom/svg/crashtests/388712-1.svg create mode 100644 dom/svg/crashtests/395616-1.html create mode 100644 dom/svg/crashtests/396618-1.html create mode 100644 dom/svg/crashtests/397017-1.html create mode 100644 dom/svg/crashtests/397551-1.svg create mode 100644 dom/svg/crashtests/397704-1.svg create mode 100644 dom/svg/crashtests/398926-both-different.svg create mode 100644 dom/svg/crashtests/398926-both-same.svg create mode 100644 dom/svg/crashtests/398926-fill.svg create mode 100644 dom/svg/crashtests/398926-stroke.svg create mode 100644 dom/svg/crashtests/405639-1.svg create mode 100644 dom/svg/crashtests/406361-1.html create mode 100644 dom/svg/crashtests/409811-1.html create mode 100644 dom/svg/crashtests/410659-1.svg create mode 100644 dom/svg/crashtests/410659-2.svg create mode 100644 dom/svg/crashtests/410659-3.svg create mode 100644 dom/svg/crashtests/413174-1.svg create mode 100644 dom/svg/crashtests/414188-1.svg create mode 100644 dom/svg/crashtests/427325-1.svg create mode 100644 dom/svg/crashtests/428228-1.svg create mode 100644 dom/svg/crashtests/428841-1.svg create mode 100644 dom/svg/crashtests/436418-mpathRoot-1.svg create mode 100644 dom/svg/crashtests/448244-1.svg create mode 100644 dom/svg/crashtests/466576-1.xhtml create mode 100644 dom/svg/crashtests/499879-1.svg create mode 100644 dom/svg/crashtests/535691-1.svg create mode 100644 dom/svg/crashtests/539167-1.svg create mode 100644 dom/svg/crashtests/573316-1.svg create mode 100644 dom/svg/crashtests/579356-1.svg create mode 100644 dom/svg/crashtests/579356-2.svg create mode 100644 dom/svg/crashtests/595608-1.svg create mode 100644 dom/svg/crashtests/601251-1.html create mode 100644 dom/svg/crashtests/601406-1.svg create mode 100644 dom/svg/crashtests/603145-1.svg create mode 100644 dom/svg/crashtests/613899-1.svg create mode 100644 dom/svg/crashtests/613899-2.svg create mode 100644 dom/svg/crashtests/719779-1.svg create mode 100644 dom/svg/crashtests/723441-1.html create mode 100644 dom/svg/crashtests/723441-resource.svg create mode 100644 dom/svg/crashtests/751515-1.svg create mode 100644 dom/svg/crashtests/761507-1.svg create mode 100644 dom/svg/crashtests/821955-1.html create mode 100644 dom/svg/crashtests/831561.html create mode 100644 dom/svg/crashtests/842463-1.html create mode 100644 dom/svg/crashtests/847138-1.svg create mode 100644 dom/svg/crashtests/864509.svg create mode 100644 dom/svg/crashtests/898915-1.svg create mode 100644 dom/svg/crashtests/crashtests.list create mode 100644 dom/svg/crashtests/invalid-image.svg create mode 100644 dom/svg/crashtests/long-clipPath-reference-chain.svg create mode 100644 dom/svg/crashtests/test_nested_svg.html create mode 100644 dom/svg/crashtests/zero-size-image.svg create mode 100644 dom/svg/moz.build create mode 100644 dom/svg/test/MutationEventChecker.js create mode 100644 dom/svg/test/a_href_destination.svg create mode 100644 dom/svg/test/a_href_helper_01.svg create mode 100644 dom/svg/test/a_href_helper_02_03.svg create mode 100644 dom/svg/test/a_href_helper_04.svg create mode 100644 dom/svg/test/a_href_helper_05.svg create mode 100644 dom/svg/test/a_href_helper_06.svg create mode 100644 dom/svg/test/a_href_helper_07.svg create mode 100644 dom/svg/test/animated-svg-image-helper.html create mode 100644 dom/svg/test/animated-svg-image-helper.svg create mode 100644 dom/svg/test/bbox-helper.svg create mode 100644 dom/svg/test/bounds-helper.svg create mode 100644 dom/svg/test/dataTypes-helper.svg create mode 100644 dom/svg/test/fragments-helper.svg create mode 100644 dom/svg/test/getBBox-method-helper.svg create mode 100644 dom/svg/test/getCTM-helper.svg create mode 100644 dom/svg/test/getSubStringLength-helper.svg create mode 100644 dom/svg/test/matrixUtils.js create mode 100644 dom/svg/test/mochitest.ini create mode 100644 dom/svg/test/object-delayed-intrinsic-size.sjs create mode 100644 dom/svg/test/pointer-events.js create mode 100644 dom/svg/test/scientific-helper.svg create mode 100644 dom/svg/test/selectSubString-helper.svg create mode 100644 dom/svg/test/switch-helper.svg create mode 100644 dom/svg/test/tearoff_with_cc_helper.html create mode 100644 dom/svg/test/test_SVGLengthList-2.xhtml create mode 100644 dom/svg/test/test_SVGLengthList.xhtml create mode 100644 dom/svg/test/test_SVGMatrix.xhtml create mode 100644 dom/svg/test/test_SVGNumberList.xhtml create mode 100644 dom/svg/test/test_SVGPointList.xhtml create mode 100644 dom/svg/test/test_SVGStringList.xhtml create mode 100644 dom/svg/test/test_SVGStyleElement.xhtml create mode 100644 dom/svg/test/test_SVGTransformList.xhtml create mode 100644 dom/svg/test/test_SVGTransformListAddition.xhtml create mode 100644 dom/svg/test/test_SVG_namespace_ids.html create mode 100644 dom/svg/test/test_SVGxxxList.xhtml create mode 100644 dom/svg/test/test_SVGxxxListIndexing.xhtml create mode 100644 dom/svg/test/test_a_href_01.xhtml create mode 100644 dom/svg/test/test_a_href_02.xhtml create mode 100644 dom/svg/test/test_animLengthObjectIdentity.xhtml create mode 100644 dom/svg/test/test_animLengthReadonly.xhtml create mode 100644 dom/svg/test/test_animLengthUnits.xhtml create mode 100644 dom/svg/test/test_bbox-changes.xhtml create mode 100644 dom/svg/test/test_bbox-with-invalid-viewBox.xhtml create mode 100644 dom/svg/test/test_bbox.xhtml create mode 100644 dom/svg/test/test_bounds.html create mode 100644 dom/svg/test/test_bug1426594.html create mode 100644 dom/svg/test/test_bug872812.html create mode 100644 dom/svg/test/test_dataTypes.html create mode 100644 dom/svg/test/test_dataTypesModEvents.html create mode 100644 dom/svg/test/test_fragments.html create mode 100644 dom/svg/test/test_getBBox-method.html create mode 100644 dom/svg/test/test_getCTM.html create mode 100644 dom/svg/test/test_getElementById.xhtml create mode 100644 dom/svg/test/test_getPathSegListAtLength_with_d_property.html create mode 100644 dom/svg/test/test_getSubStringLength.xhtml create mode 100644 dom/svg/test/test_getTotalLength.xhtml create mode 100644 dom/svg/test/test_hit-testing-and-viewbox.xhtml create mode 100644 dom/svg/test/test_lang.xhtml create mode 100644 dom/svg/test/test_length.xhtml create mode 100644 dom/svg/test/test_lengthParsing.html create mode 100644 dom/svg/test/test_markerOrient.xhtml create mode 100644 dom/svg/test/test_non-scaling-stroke.html create mode 100644 dom/svg/test/test_nonAnimStrings.xhtml create mode 100644 dom/svg/test/test_object-delayed-intrinsic-size.html create mode 100644 dom/svg/test/test_onerror.xhtml create mode 100644 dom/svg/test/test_onload.xhtml create mode 100644 dom/svg/test/test_onload2.xhtml create mode 100644 dom/svg/test/test_pairParsing.html create mode 100644 dom/svg/test/test_pathAnimInterpolation.xhtml create mode 100644 dom/svg/test/test_pointAtLength.xhtml create mode 100644 dom/svg/test/test_pointer-events-1a.xhtml create mode 100644 dom/svg/test/test_pointer-events-1b.xhtml create mode 100644 dom/svg/test/test_pointer-events-2.xhtml create mode 100644 dom/svg/test/test_pointer-events-3.xhtml create mode 100644 dom/svg/test/test_pointer-events-4.xhtml create mode 100644 dom/svg/test/test_pointer-events-6.xhtml create mode 100644 dom/svg/test/test_pointer-events-7.xhtml create mode 100644 dom/svg/test/test_scientific.html create mode 100644 dom/svg/test/test_selectSubString.xhtml create mode 100644 dom/svg/test/test_stroke-hit-testing.xhtml create mode 100644 dom/svg/test/test_stroke-linecap-hit-testing.xhtml create mode 100644 dom/svg/test/test_style_sheet.html create mode 100644 dom/svg/test/test_switch.xhtml create mode 100644 dom/svg/test/test_tabindex.html create mode 100644 dom/svg/test/test_tearoff_with_cc.html create mode 100644 dom/svg/test/test_text.html create mode 100644 dom/svg/test/test_text_2.html create mode 100644 dom/svg/test/test_text_dirty.html create mode 100644 dom/svg/test/test_text_lengthAdjust.html create mode 100644 dom/svg/test/test_text_scaled.html create mode 100644 dom/svg/test/test_text_selection.html create mode 100644 dom/svg/test/test_text_update.html create mode 100644 dom/svg/test/test_transform.xhtml create mode 100644 dom/svg/test/test_transformParsing.html create mode 100644 dom/svg/test/test_use_with_hsts.html create mode 100644 dom/svg/test/test_valueAsString.xhtml create mode 100644 dom/svg/test/test_valueLeaks.xhtml create mode 100644 dom/svg/test/test_viewBox.html create mode 100644 dom/svg/test/test_viewport.html create mode 100644 dom/svg/test/text-helper-scaled.svg create mode 100644 dom/svg/test/text-helper-selection.svg create mode 100644 dom/svg/test/text-helper.svg create mode 100644 dom/svg/test/use-with-hsts-helper.html create mode 100644 dom/svg/test/use-with-hsts-helper.html^headers^ create mode 100644 dom/svg/test/viewport-helper.svg (limited to 'dom/svg') diff --git a/dom/svg/DOMSVGAngle.cpp b/dom/svg/DOMSVGAngle.cpp new file mode 100644 index 0000000000..e22b62c1e9 --- /dev/null +++ b/dom/svg/DOMSVGAngle.cpp @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAngle.h" +#include "SVGAnimatedOrient.h" +#include "mozilla/dom/SVGAngleBinding.h" +#include "mozilla/dom/SVGSVGElement.h" + +namespace mozilla::dom { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAngle, mSVGElement) + +DOMSVGAngle::DOMSVGAngle(SVGSVGElement* aSVGElement) + : mSVGElement(aSVGElement), mType(AngleType::CreatedValue) { + mVal = new SVGAnimatedOrient(); + mVal->Init(); +} + +JSObject* DOMSVGAngle::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGAngle_Binding::Wrap(aCx, this, aGivenProto); +} + +uint16_t DOMSVGAngle::UnitType() const { + uint16_t unitType; + if (mType == AngleType::AnimValue) { + mSVGElement->FlushAnimations(); + unitType = mVal->mAnimValUnit; + } else { + unitType = mVal->mBaseValUnit; + } + return SVGAnimatedOrient::IsValidUnitType(unitType) + ? unitType + : SVGAngle_Binding::SVG_ANGLETYPE_UNKNOWN; +} + +float DOMSVGAngle::Value() const { + if (mType == AngleType::AnimValue) { + mSVGElement->FlushAnimations(); + return mVal->GetAnimValue(); + } + return mVal->GetBaseValue(); +} + +void DOMSVGAngle::SetValue(float aValue, ErrorResult& rv) { + if (mType == AngleType::AnimValue) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + bool isBaseVal = mType == AngleType::BaseValue; + mVal->SetBaseValue(aValue, mVal->mBaseValUnit, + isBaseVal ? mSVGElement.get() : nullptr, isBaseVal); +} + +float DOMSVGAngle::ValueInSpecifiedUnits() const { + if (mType == AngleType::AnimValue) { + return mVal->mAnimVal; + } + return mVal->mBaseVal; +} + +void DOMSVGAngle::SetValueInSpecifiedUnits(float aValue, ErrorResult& rv) { + if (mType == AngleType::AnimValue) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + if (mType == AngleType::BaseValue) { + mVal->SetBaseValueInSpecifiedUnits(aValue, mSVGElement); + } else { + mVal->mBaseVal = aValue; + } +} + +void DOMSVGAngle::NewValueSpecifiedUnits(uint16_t unitType, + float valueInSpecifiedUnits, + ErrorResult& rv) { + if (mType == AngleType::AnimValue) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + rv = mVal->NewValueSpecifiedUnits( + unitType, valueInSpecifiedUnits, + mType == AngleType::BaseValue ? mSVGElement.get() : nullptr); +} + +void DOMSVGAngle::ConvertToSpecifiedUnits(uint16_t unitType, ErrorResult& rv) { + if (mType == AngleType::AnimValue) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + rv = mVal->ConvertToSpecifiedUnits( + unitType, mType == AngleType::BaseValue ? mSVGElement.get() : nullptr); +} + +void DOMSVGAngle::SetValueAsString(const nsAString& aValue, ErrorResult& rv) { + if (mType == AngleType::AnimValue) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + bool isBaseVal = mType == AngleType::BaseValue; + rv = mVal->SetBaseValueString(aValue, isBaseVal ? mSVGElement.get() : nullptr, + isBaseVal); +} + +void DOMSVGAngle::GetValueAsString(nsAString& aValue) { + if (mType == AngleType::AnimValue) { + mSVGElement->FlushAnimations(); + mVal->GetAnimAngleValueString(aValue); + } else { + mVal->GetBaseAngleValueString(aValue); + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAngle.h b/dom/svg/DOMSVGAngle.h new file mode 100644 index 0000000000..ed2cb23b64 --- /dev/null +++ b/dom/svg/DOMSVGAngle.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANGLE_H_ +#define DOM_SVG_DOMSVGANGLE_H_ + +#include "nsWrapperCache.h" +#include "SVGElement.h" +#include "mozilla/Attributes.h" + +namespace mozilla { + +class SVGAnimatedOrient; + +namespace dom { +class SVGSVGElement; + +class DOMSVGAngle final : public nsWrapperCache { + public: + enum class AngleType : int8_t { BaseValue, AnimValue, CreatedValue }; + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAngle) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAngle) + + /** + * Generic ctor for DOMSVGAngle objects that are created for an attribute. + */ + DOMSVGAngle(SVGAnimatedOrient* aVal, SVGElement* aSVGElement, AngleType aType) + : mVal(aVal), mSVGElement(aSVGElement), mType(aType) {} + + /** + * Ctor for creating the objects returned by SVGSVGElement.createSVGAngle(), + * which do not initially belong to an attribute. + */ + explicit DOMSVGAngle(SVGSVGElement* aSVGElement); + + // WebIDL + SVGElement* GetParentObject() { return mSVGElement; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + uint16_t UnitType() const; + float Value() const; + void GetValueAsString(nsAString& aValue); + void SetValue(float aValue, ErrorResult& rv); + float ValueInSpecifiedUnits() const; + void SetValueInSpecifiedUnits(float aValue, ErrorResult& rv); + void SetValueAsString(const nsAString& aValue, ErrorResult& rv); + void NewValueSpecifiedUnits(uint16_t unitType, float value, ErrorResult& rv); + void ConvertToSpecifiedUnits(uint16_t unitType, ErrorResult& rv); + + protected: + ~DOMSVGAngle(); + + SVGAnimatedOrient* mVal; // if mType is CreatedValue, we own the angle. + // Otherwise, the element does. + RefPtr mSVGElement; + AngleType mType; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_DOMSVGANGLE_H_ diff --git a/dom/svg/DOMSVGAnimatedAngle.cpp b/dom/svg/DOMSVGAnimatedAngle.cpp new file mode 100644 index 0000000000..4762f79b20 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedAngle.cpp @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAnimatedAngle.h" + +#include "SVGAnimatedOrient.h" +#include "mozilla/dom/SVGAnimatedAngleBinding.h" + +namespace mozilla::dom { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAnimatedAngle, mSVGElement) + +JSObject* DOMSVGAnimatedAngle::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGAnimatedAngle_Binding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed DOMSVGAnimatedAngle::BaseVal() { + return mVal->ToDOMBaseVal(mSVGElement); +} + +already_AddRefed DOMSVGAnimatedAngle::AnimVal() { + return mVal->ToDOMAnimVal(mSVGElement); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAnimatedAngle.h b/dom/svg/DOMSVGAnimatedAngle.h new file mode 100644 index 0000000000..d5190010fa --- /dev/null +++ b/dom/svg/DOMSVGAnimatedAngle.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANIMATEDANGLE_H_ +#define DOM_SVG_DOMSVGANIMATEDANGLE_H_ + +#include "nsWrapperCache.h" +#include "SVGElement.h" +#include "mozilla/Attributes.h" + +namespace mozilla { + +class SVGAnimatedOrient; + +namespace dom { + +class DOMSVGAngle; + +class DOMSVGAnimatedAngle final : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAnimatedAngle) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAnimatedAngle) + + DOMSVGAnimatedAngle(SVGAnimatedOrient* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // WebIDL + SVGElement* GetParentObject() { return mSVGElement; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + already_AddRefed BaseVal(); + already_AddRefed AnimVal(); + + protected: + ~DOMSVGAnimatedAngle(); + + SVGAnimatedOrient* mVal; // kept alive because it belongs to content + RefPtr mSVGElement; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_DOMSVGANIMATEDANGLE_H_ diff --git a/dom/svg/DOMSVGAnimatedBoolean.cpp b/dom/svg/DOMSVGAnimatedBoolean.cpp new file mode 100644 index 0000000000..d35429ac65 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedBoolean.cpp @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAnimatedBoolean.h" + +#include "mozilla/dom/SVGAnimatedBooleanBinding.h" + +namespace mozilla::dom { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAnimatedBoolean, + mSVGElement) + +JSObject* DOMSVGAnimatedBoolean::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGAnimatedBoolean_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAnimatedBoolean.h b/dom/svg/DOMSVGAnimatedBoolean.h new file mode 100644 index 0000000000..7e1b5caae4 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedBoolean.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANIMATEDBOOLEAN_H_ +#define DOM_SVG_DOMSVGANIMATEDBOOLEAN_H_ + +#include "SVGAnimatedBoolean.h" +#include "nsWrapperCache.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/SVGElement.h" + +namespace mozilla::dom { + +class DOMSVGAnimatedBoolean final : public nsWrapperCache { + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAnimatedBoolean) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAnimatedBoolean) + + DOMSVGAnimatedBoolean(SVGAnimatedBoolean* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // WebIDL + SVGElement* GetParentObject() const { return mSVGElement; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + bool BaseVal() const { return mVal->GetBaseValue(); } + void SetBaseVal(bool aValue) { mVal->SetBaseValue(aValue, mSVGElement); } + bool AnimVal() const { + mSVGElement->FlushAnimations(); + return mVal->GetAnimValue(); + } + + protected: + ~DOMSVGAnimatedBoolean(); + + SVGAnimatedBoolean* mVal; // kept alive because it belongs to content + RefPtr mSVGElement; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_DOMSVGANIMATEDBOOLEAN_H_ diff --git a/dom/svg/DOMSVGAnimatedEnumeration.cpp b/dom/svg/DOMSVGAnimatedEnumeration.cpp new file mode 100644 index 0000000000..a5d3f16466 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedEnumeration.cpp @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAnimatedEnumeration.h" + +#include "mozilla/dom/SVGAnimatedEnumerationBinding.h" + +namespace mozilla::dom { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAnimatedEnumeration, + mSVGElement) + +JSObject* DOMSVGAnimatedEnumeration::WrapObject( + JSContext* aCx, JS::Handle aGivenProto) { + return SVGAnimatedEnumeration_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAnimatedEnumeration.h b/dom/svg/DOMSVGAnimatedEnumeration.h new file mode 100644 index 0000000000..fb8354654b --- /dev/null +++ b/dom/svg/DOMSVGAnimatedEnumeration.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANIMATEDENUMERATION_H_ +#define DOM_SVG_DOMSVGANIMATEDENUMERATION_H_ + +#include "nsWrapperCache.h" + +#include "SVGElement.h" + +namespace mozilla::dom { + +class DOMSVGAnimatedEnumeration : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAnimatedEnumeration) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAnimatedEnumeration) + + SVGElement* GetParentObject() const { return mSVGElement; } + + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) final; + + virtual uint16_t BaseVal() = 0; + virtual void SetBaseVal(uint16_t aBaseVal, ErrorResult& aRv) = 0; + virtual uint16_t AnimVal() = 0; + + protected: + explicit DOMSVGAnimatedEnumeration(SVGElement* aSVGElement) + : mSVGElement(aSVGElement) {} + virtual ~DOMSVGAnimatedEnumeration() = default; + + RefPtr mSVGElement; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_DOMSVGANIMATEDENUMERATION_H_ diff --git a/dom/svg/DOMSVGAnimatedInteger.cpp b/dom/svg/DOMSVGAnimatedInteger.cpp new file mode 100644 index 0000000000..94018ecc39 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedInteger.cpp @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAnimatedInteger.h" + +#include "mozilla/dom/SVGAnimatedIntegerBinding.h" + +namespace mozilla::dom { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAnimatedInteger, + mSVGElement) + +JSObject* DOMSVGAnimatedInteger::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGAnimatedInteger_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAnimatedInteger.h b/dom/svg/DOMSVGAnimatedInteger.h new file mode 100644 index 0000000000..c4c24fed1c --- /dev/null +++ b/dom/svg/DOMSVGAnimatedInteger.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANIMATEDINTEGER_H_ +#define DOM_SVG_DOMSVGANIMATEDINTEGER_H_ + +#include "nsWrapperCache.h" + +#include "SVGElement.h" + +namespace mozilla::dom { + +class DOMSVGAnimatedInteger : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAnimatedInteger) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAnimatedInteger) + + SVGElement* GetParentObject() const { return mSVGElement; } + + JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) final; + + virtual int32_t BaseVal() = 0; + virtual void SetBaseVal(int32_t aBaseVal) = 0; + virtual int32_t AnimVal() = 0; + + protected: + explicit DOMSVGAnimatedInteger(SVGElement* aSVGElement) + : mSVGElement(aSVGElement) {} + virtual ~DOMSVGAnimatedInteger() = default; + + RefPtr mSVGElement; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_DOMSVGANIMATEDINTEGER_H_ diff --git a/dom/svg/DOMSVGAnimatedLength.cpp b/dom/svg/DOMSVGAnimatedLength.cpp new file mode 100644 index 0000000000..53bbfea064 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedLength.cpp @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAnimatedLength.h" + +#include "mozilla/dom/SVGAnimatedLengthBinding.h" +#include "SVGAnimatedLength.h" +#include "DOMSVGLength.h" + +namespace mozilla::dom { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAnimatedLength, + mSVGElement) + +JSObject* DOMSVGAnimatedLength::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGAnimatedLength_Binding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed DOMSVGAnimatedLength::BaseVal() { + return mVal->ToDOMBaseVal(mSVGElement); +} + +already_AddRefed DOMSVGAnimatedLength::AnimVal() { + return mVal->ToDOMAnimVal(mSVGElement); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAnimatedLength.h b/dom/svg/DOMSVGAnimatedLength.h new file mode 100644 index 0000000000..09df1d0ce4 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedLength.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANIMATEDLENGTH_H_ +#define DOM_SVG_DOMSVGANIMATEDLENGTH_H_ + +#include "mozilla/Attributes.h" +#include "SVGElement.h" + +namespace mozilla { + +class SVGAnimatedLength; + +namespace dom { + +class DOMSVGLength; + +class DOMSVGAnimatedLength final : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAnimatedLength) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAnimatedLength) + + DOMSVGAnimatedLength(SVGAnimatedLength* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // WebIDL + SVGElement* GetParentObject() { return mSVGElement; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + already_AddRefed BaseVal(); + already_AddRefed AnimVal(); + + protected: + ~DOMSVGAnimatedLength(); + + SVGAnimatedLength* mVal; // kept alive because it belongs to content + RefPtr mSVGElement; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_DOMSVGANIMATEDLENGTH_H_ diff --git a/dom/svg/DOMSVGAnimatedLengthList.cpp b/dom/svg/DOMSVGAnimatedLengthList.cpp new file mode 100644 index 0000000000..b64b923745 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedLengthList.cpp @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMSVGAnimatedLengthList.h" + +#include "DOMSVGLengthList.h" +#include "SVGAnimatedLengthList.h" +#include "SVGAttrTearoffTable.h" +#include "mozilla/dom/SVGAnimatedLengthListBinding.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/RefPtr.h" + +// See the architecture comment in this file's header. + +namespace mozilla::dom { + +static inline SVGAttrTearoffTable& +SVGAnimatedLengthListTearoffTable() { + static SVGAttrTearoffTable + sSVGAnimatedLengthListTearoffTable; + return sSVGAnimatedLengthListTearoffTable; +} + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGAnimatedLengthList, + mElement) + +JSObject* DOMSVGAnimatedLengthList::WrapObject( + JSContext* aCx, JS::Handle aGivenProto) { + return dom::SVGAnimatedLengthList_Binding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed DOMSVGAnimatedLengthList::BaseVal() { + if (!mBaseVal) { + mBaseVal = new DOMSVGLengthList(this, InternalAList().GetBaseValue()); + } + RefPtr baseVal = mBaseVal; + return baseVal.forget(); +} + +already_AddRefed DOMSVGAnimatedLengthList::AnimVal() { + if (!mAnimVal) { + mAnimVal = new DOMSVGLengthList(this, InternalAList().GetAnimValue()); + } + RefPtr animVal = mAnimVal; + return animVal.forget(); +} + +/* static */ +already_AddRefed +DOMSVGAnimatedLengthList::GetDOMWrapper(SVGAnimatedLengthList* aList, + dom::SVGElement* aElement, + uint8_t aAttrEnum, uint8_t aAxis) { + RefPtr wrapper = + SVGAnimatedLengthListTearoffTable().GetTearoff(aList); + if (!wrapper) { + wrapper = new DOMSVGAnimatedLengthList(aElement, aAttrEnum, aAxis); + SVGAnimatedLengthListTearoffTable().AddTearoff(aList, wrapper); + } + return wrapper.forget(); +} + +/* static */ +DOMSVGAnimatedLengthList* DOMSVGAnimatedLengthList::GetDOMWrapperIfExists( + SVGAnimatedLengthList* aList) { + return SVGAnimatedLengthListTearoffTable().GetTearoff(aList); +} + +DOMSVGAnimatedLengthList::~DOMSVGAnimatedLengthList() { + // Script no longer has any references to us, to our base/animVal objects, or + // to any of their list items. + SVGAnimatedLengthListTearoffTable().RemoveTearoff(&InternalAList()); +} + +void DOMSVGAnimatedLengthList::InternalBaseValListWillChangeTo( + const SVGLengthList& aNewValue) { + // When the number of items in our internal counterpart's baseVal changes, + // we MUST keep our baseVal in sync. If we don't, script will either see a + // list that is too short and be unable to access indexes that should be + // valid, or else, MUCH WORSE, script will see a list that is too long and be + // able to access "items" at indexes that are out of bounds (read/write to + // bad memory)!! + + RefPtr kungFuDeathGrip; + if (mBaseVal) { + if (aNewValue.Length() < mBaseVal->LengthNoFlush()) { + // InternalListLengthWillChange might clear last reference to |this|. + // Retain a temporary reference to keep from dying before returning. + kungFuDeathGrip = this; + } + mBaseVal->InternalListLengthWillChange(aNewValue.Length()); + } + + // If our attribute is not animating, then our animVal mirrors our baseVal + // and we must sync its length too. (If our attribute is animating, then the + // SMIL engine takes care of calling InternalAnimValListWillChangeTo() if + // necessary.) + + if (!IsAnimating()) { + InternalAnimValListWillChangeTo(aNewValue); + } +} + +void DOMSVGAnimatedLengthList::InternalAnimValListWillChangeTo( + const SVGLengthList& aNewValue) { + if (mAnimVal) { + mAnimVal->InternalListLengthWillChange(aNewValue.Length()); + } +} + +bool DOMSVGAnimatedLengthList::IsAnimating() const { + return InternalAList().IsAnimating(); +} + +SVGAnimatedLengthList& DOMSVGAnimatedLengthList::InternalAList() { + return *mElement->GetAnimatedLengthList(mAttrEnum); +} + +const SVGAnimatedLengthList& DOMSVGAnimatedLengthList::InternalAList() const { + return *mElement->GetAnimatedLengthList(mAttrEnum); +} + +} // namespace mozilla::dom diff --git a/dom/svg/DOMSVGAnimatedLengthList.h b/dom/svg/DOMSVGAnimatedLengthList.h new file mode 100644 index 0000000000..04551a41b2 --- /dev/null +++ b/dom/svg/DOMSVGAnimatedLengthList.h @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_DOMSVGANIMATEDLENGTHLIST_H_ +#define DOM_SVG_DOMSVGANIMATEDLENGTHLIST_H_ + +#include "nsCycleCollectionParticipant.h" +#include "SVGElement.h" +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +class SVGAnimatedLengthList; +class SVGLengthList; + +namespace dom { + +class DOMSVGLengthList; + +/** + * Class DOMSVGAnimatedLengthList + * + * This class is used to create the DOM tearoff objects that wrap internal + * SVGAnimatedLengthList objects. We have this internal-DOM split because DOM + * classes are relatively heavy-weight objects with non-optimal interfaces for + * internal code, and they're relatively infrequently used. Having separate + * internal and DOM classes does add complexity - especially for lists where + * the internal list and DOM lists (and their items) need to be kept in sync - + * but it keeps the internal classes light and fast, and in 99% of cases + * they're all that's used. DOM wrappers are only instantiated when script + * demands it. + * + * Ownership model: + * + * The diagram below shows the ownership model between the various DOM objects + * in the tree of DOM objects that correspond to an SVG length list attribute. + * The angled brackets ">" and "<" denote a reference from one object to + * another, where the "!" character denotes a strong reference, and the "~" + * character denotes a weak reference. + * + * .---- no viewbox rect + return false; + } + rect = &mBaseVal; + } + + return !rect->none && rect->width >= 0 && rect->height >= 0; +} + +void SVGAnimatedViewBox::SetAnimValue(const SVGViewBox& aRect, + SVGElement* aSVGElement) { + if (!mAnimVal) { + // it's okay if allocation fails - and no point in reporting that + mAnimVal = MakeUnique(aRect); + } else { + if (aRect == *mAnimVal) { + return; + } + *mAnimVal = aRect; + } + aSVGElement->DidAnimateViewBox(); +} + +void SVGAnimatedViewBox::SetBaseValue(const SVGViewBox& aRect, + SVGElement* aSVGElement) { + if (!mHasBaseVal || mBaseVal == aRect) { + // This method is used to set a single x, y, width + // or height value. It can't create a base value + // as the other components may be undefined. We record + // the new value though, so as not to lose data. + mBaseVal = aRect; + return; + } + + AutoChangeViewBoxNotifier notifier(this, aSVGElement); + + mBaseVal = aRect; + mHasBaseVal = true; +} + +nsresult SVGAnimatedViewBox::SetBaseValueString(const nsAString& aValue, + SVGElement* aSVGElement, + bool aDoSetAttr) { + SVGViewBox viewBox; + + nsresult rv = SVGViewBox::FromString(aValue, &viewBox); + if (NS_FAILED(rv)) { + return rv; + } + // Comparison against mBaseVal is only valid if we currently have a base val. + if (mHasBaseVal && viewBox == mBaseVal) { + return NS_OK; + } + + AutoChangeViewBoxNotifier notifier(this, aSVGElement, aDoSetAttr); + mHasBaseVal = true; + mBaseVal = viewBox; + + return NS_OK; +} + +void SVGAnimatedViewBox::GetBaseValueString(nsAString& aValue) const { + if (mBaseVal.none) { + aValue.AssignLiteral("none"); + return; + } + nsTextFormatter::ssprintf(aValue, u"%g %g %g %g", (double)mBaseVal.x, + (double)mBaseVal.y, (double)mBaseVal.width, + (double)mBaseVal.height); +} + +already_AddRefed SVGAnimatedViewBox::ToSVGAnimatedRect( + SVGElement* aSVGElement) { + RefPtr domAnimatedRect = + sSVGAnimatedRectTearoffTable.GetTearoff(this); + if (!domAnimatedRect) { + domAnimatedRect = new SVGAnimatedRect(this, aSVGElement); + sSVGAnimatedRectTearoffTable.AddTearoff(this, domAnimatedRect); + } + + return domAnimatedRect.forget(); +} + +already_AddRefed SVGAnimatedViewBox::ToDOMBaseVal( + SVGElement* aSVGElement) { + if (!mHasBaseVal || mBaseVal.none) { + return nullptr; + } + + RefPtr domBaseVal = sBaseSVGViewBoxTearoffTable.GetTearoff(this); + if (!domBaseVal) { + domBaseVal = new SVGRect(this, aSVGElement, SVGRect::RectType::BaseValue); + sBaseSVGViewBoxTearoffTable.AddTearoff(this, domBaseVal); + } + + return domBaseVal.forget(); +} + +SVGRect::~SVGRect() { + switch (mType) { + case RectType::BaseValue: + sBaseSVGViewBoxTearoffTable.RemoveTearoff(mVal); + break; + case RectType::AnimValue: + sAnimSVGViewBoxTearoffTable.RemoveTearoff(mVal); + break; + default: + break; + } +} + +already_AddRefed SVGAnimatedViewBox::ToDOMAnimVal( + SVGElement* aSVGElement) { + if ((mAnimVal && mAnimVal->none) || + (!mAnimVal && (!mHasBaseVal || mBaseVal.none))) { + return nullptr; + } + + RefPtr domAnimVal = sAnimSVGViewBoxTearoffTable.GetTearoff(this); + if (!domAnimVal) { + domAnimVal = new SVGRect(this, aSVGElement, SVGRect::RectType::AnimValue); + sAnimSVGViewBoxTearoffTable.AddTearoff(this, domAnimVal); + } + + return domAnimVal.forget(); +} + +UniquePtr SVGAnimatedViewBox::ToSMILAttr(SVGElement* aSVGElement) { + return MakeUnique(this, aSVGElement); +} + +nsresult SVGAnimatedViewBox::SMILViewBox ::ValueFromString( + const nsAString& aStr, const SVGAnimationElement* /*aSrcElement*/, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + SVGViewBox viewBox; + nsresult res = SVGViewBox::FromString(aStr, &viewBox); + if (NS_FAILED(res)) { + return res; + } + SMILValue val(&SVGViewBoxSMILType::sSingleton); + *static_cast(val.mU.mPtr) = viewBox; + aValue = std::move(val); + + return NS_OK; +} + +SMILValue SVGAnimatedViewBox::SMILViewBox::GetBaseValue() const { + SMILValue val(&SVGViewBoxSMILType::sSingleton); + *static_cast(val.mU.mPtr) = mVal->mBaseVal; + return val; +} + +void SVGAnimatedViewBox::SMILViewBox::ClearAnimValue() { + if (mVal->mAnimVal) { + mVal->mAnimVal = nullptr; + mSVGElement->DidAnimateViewBox(); + } +} + +nsresult SVGAnimatedViewBox::SMILViewBox::SetAnimValue( + const SMILValue& aValue) { + NS_ASSERTION(aValue.mType == &SVGViewBoxSMILType::sSingleton, + "Unexpected type to assign animated value"); + if (aValue.mType == &SVGViewBoxSMILType::sSingleton) { + SVGViewBox& vb = *static_cast(aValue.mU.mPtr); + mVal->SetAnimValue(vb, mSVGElement); + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGAnimatedViewBox.h b/dom/svg/SVGAnimatedViewBox.h new file mode 100644 index 0000000000..edc67e1d24 --- /dev/null +++ b/dom/svg/SVGAnimatedViewBox.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATEDVIEWBOX_H_ +#define DOM_SVG_SVGANIMATEDVIEWBOX_H_ + +#include "nsCycleCollectionParticipant.h" +#include "nsError.h" +#include "SVGAttrTearoffTable.h" +#include "mozilla/Attributes.h" +#include "mozilla/SMILAttr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/SVGAnimatedRect.h" + +namespace mozilla { + +class SMILValue; + +namespace dom { +class SVGRect; +class SVGAnimationElement; +class SVGElement; +} // namespace dom + +struct SVGViewBox { + float x, y; + float width, height; + bool none; + + SVGViewBox() : x(0.0), y(0.0), width(0.0), height(0.0), none(true) {} + SVGViewBox(float aX, float aY, float aWidth, float aHeight) + : x(aX), y(aY), width(aWidth), height(aHeight), none(false) {} + bool operator==(const SVGViewBox& aOther) const; + + static nsresult FromString(const nsAString& aStr, SVGViewBox* aViewBox); +}; + +class SVGAnimatedViewBox { + public: + friend class AutoChangeViewBoxNotifier; + using SVGElement = dom::SVGElement; + + void Init(); + + /** + * Returns true if the corresponding "viewBox" attribute defined a rectangle + * with finite values and nonnegative width/height. + * Returns false if the viewBox was set to an invalid + * string, or if any of the four rect values were too big to store in a + * float, or the width/height are negative. + */ + bool HasRect() const; + + /** + * Returns true if the corresponding "viewBox" attribute either defined a + * rectangle with finite values or the special "none" value. + */ + bool IsExplicitlySet() const { + if (mAnimVal || mHasBaseVal) { + const SVGViewBox& rect = GetAnimValue(); + return rect.none || (rect.width >= 0 && rect.height >= 0); + } + return false; + } + + const SVGViewBox& GetBaseValue() const { return mBaseVal; } + void SetBaseValue(const SVGViewBox& aRect, SVGElement* aSVGElement); + const SVGViewBox& GetAnimValue() const { + return mAnimVal ? *mAnimVal : mBaseVal; + } + void SetAnimValue(const SVGViewBox& aRect, SVGElement* aSVGElement); + + nsresult SetBaseValueString(const nsAString& aValue, SVGElement* aSVGElement, + bool aDoSetAttr); + void GetBaseValueString(nsAString& aValue) const; + + already_AddRefed ToSVGAnimatedRect( + SVGElement* aSVGElement); + + already_AddRefed ToDOMBaseVal(SVGElement* aSVGElement); + + already_AddRefed ToDOMAnimVal(SVGElement* aSVGElement); + + UniquePtr ToSMILAttr(SVGElement* aSVGElement); + + private: + SVGViewBox mBaseVal; + UniquePtr mAnimVal; + bool mHasBaseVal; + + public: + struct SMILViewBox : public SMILAttr { + public: + SMILViewBox(SVGAnimatedViewBox* aVal, SVGElement* aSVGElement) + : mVal(aVal), mSVGElement(aSVGElement) {} + + // These will stay alive because a SMILAttr only lives as long + // as the Compositing step, and DOM elements don't get a chance to + // die during that. + SVGAnimatedViewBox* mVal; + SVGElement* mSVGElement; + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + void ClearAnimValue() override; + nsresult SetAnimValue(const SMILValue& aValue) override; + }; + + static SVGAttrTearoffTable + sSVGAnimatedRectTearoffTable; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGANIMATEDVIEWBOX_H_ diff --git a/dom/svg/SVGAnimationElement.cpp b/dom/svg/SVGAnimationElement.cpp new file mode 100644 index 0000000000..3d743a461e --- /dev/null +++ b/dom/svg/SVGAnimationElement.cpp @@ -0,0 +1,367 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGAnimationElement.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/dom/BindContext.h" +#include "mozilla/dom/ElementInlines.h" +#include "mozilla/SMILAnimationController.h" +#include "mozilla/SMILAnimationFunction.h" +#include "mozilla/SMILTimeContainer.h" +#include "mozilla/SVGObserverUtils.h" +#include "nsContentUtils.h" +#include "nsIContentInlines.h" +#include "nsIReferrerInfo.h" +#include "nsIURI.h" +#include "prtime.h" + +namespace mozilla::dom { + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ADDREF_INHERITED(SVGAnimationElement, SVGAnimationElementBase) +NS_IMPL_RELEASE_INHERITED(SVGAnimationElement, SVGAnimationElementBase) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGAnimationElement) + NS_INTERFACE_MAP_ENTRY(mozilla::dom::SVGTests) +NS_INTERFACE_MAP_END_INHERITING(SVGAnimationElementBase) + +NS_IMPL_CYCLE_COLLECTION_INHERITED(SVGAnimationElement, SVGAnimationElementBase, + mHrefTarget, mTimedElement) + +//---------------------------------------------------------------------- +// Implementation + +SVGAnimationElement::SVGAnimationElement( + already_AddRefed&& aNodeInfo) + : SVGAnimationElementBase(std::move(aNodeInfo)), mHrefTarget(this) {} + +nsresult SVGAnimationElement::Init() { + nsresult rv = SVGAnimationElementBase::Init(); + NS_ENSURE_SUCCESS(rv, rv); + + mTimedElement.SetAnimationElement(this); + AnimationFunction().SetAnimationElement(this); + mTimedElement.SetTimeClient(&AnimationFunction()); + + return NS_OK; +} + +//---------------------------------------------------------------------- + +Element* SVGAnimationElement::GetTargetElementContent() { + if (HasAttr(kNameSpaceID_XLink, nsGkAtoms::href) || + HasAttr(kNameSpaceID_None, nsGkAtoms::href)) { + return mHrefTarget.get(); + } + MOZ_ASSERT(!mHrefTarget.get(), + "We shouldn't have a href target " + "if we don't have an xlink:href or href attribute"); + + // No "href" or "xlink:href" attribute --> I should target my parent. + // + // Note that we want to use GetParentElement instead of the flattened tree to + // allow , for example. + return GetParentElement(); +} + +bool SVGAnimationElement::GetTargetAttributeName(int32_t* aNamespaceID, + nsAtom** aLocalName) const { + const nsAttrValue* nameAttr = mAttrs.GetAttr(nsGkAtoms::attributeName); + + if (!nameAttr) return false; + + NS_ASSERTION(nameAttr->Type() == nsAttrValue::eAtom, + "attributeName should have been parsed as an atom"); + + return NS_SUCCEEDED(nsContentUtils::SplitQName( + this, nsDependentAtomString(nameAttr->GetAtomValue()), aNamespaceID, + aLocalName)); +} + +SMILTimedElement& SVGAnimationElement::TimedElement() { return mTimedElement; } + +SVGElement* SVGAnimationElement::GetTargetElement() { + FlushAnimations(); + + // We'll just call the other GetTargetElement method, and QI to the right type + return SVGElement::FromNodeOrNull(GetTargetElementContent()); +} + +float SVGAnimationElement::GetStartTime(ErrorResult& rv) { + FlushAnimations(); + + SMILTimeValue startTime = mTimedElement.GetStartTime(); + if (!startTime.IsDefinite()) { + rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return 0.f; + } + + return float(double(startTime.GetMillis()) / PR_MSEC_PER_SEC); +} + +float SVGAnimationElement::GetCurrentTimeAsFloat() { + // Not necessary to call FlushAnimations() for this + + SMILTimeContainer* root = GetTimeContainer(); + if (root) { + return float(double(root->GetCurrentTimeAsSMILTime()) / PR_MSEC_PER_SEC); + } + + return 0.0f; +} + +float SVGAnimationElement::GetSimpleDuration(ErrorResult& rv) { + // Not necessary to call FlushAnimations() for this + + SMILTimeValue simpleDur = mTimedElement.GetSimpleDuration(); + if (!simpleDur.IsDefinite()) { + rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return 0.f; + } + + return float(double(simpleDur.GetMillis()) / PR_MSEC_PER_SEC); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +nsresult SVGAnimationElement::BindToTree(BindContext& aContext, + nsINode& aParent) { + MOZ_ASSERT(!mHrefTarget.get(), + "Shouldn't have href-target yet (or it should've been cleared)"); + nsresult rv = SVGAnimationElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + // Add myself to the animation controller's master set of animation elements. + if (Document* doc = aContext.GetComposedDoc()) { + if (SMILAnimationController* controller = doc->GetAnimationController()) { + controller->RegisterAnimationElement(this); + } + const nsAttrValue* href = + HasAttr(kNameSpaceID_None, nsGkAtoms::href) + ? mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_None) + : mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink); + if (href) { + nsAutoString hrefStr; + href->ToString(hrefStr); + + UpdateHrefTarget(hrefStr); + } + + mTimedElement.BindToTree(*this); + } + + AnimationNeedsResample(); + + return NS_OK; +} + +void SVGAnimationElement::UnbindFromTree(bool aNullParent) { + SMILAnimationController* controller = OwnerDoc()->GetAnimationController(); + if (controller) { + controller->UnregisterAnimationElement(this); + } + + mHrefTarget.Unlink(); + mTimedElement.DissolveReferences(); + + AnimationNeedsResample(); + + SVGAnimationElementBase::UnbindFromTree(aNullParent); +} + +bool SVGAnimationElement::ParseAttribute(int32_t aNamespaceID, + nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) { + if (aNamespaceID == kNameSpaceID_None) { + // Deal with target-related attributes here + if (aAttribute == nsGkAtoms::attributeName) { + aResult.ParseAtom(aValue); + AnimationNeedsResample(); + return true; + } + + nsresult rv = NS_ERROR_FAILURE; + + // First let the animation function try to parse it... + bool foundMatch = + AnimationFunction().SetAttr(aAttribute, aValue, aResult, &rv); + + // ... and if that didn't recognize the attribute, let the timed element + // try to parse it. + if (!foundMatch) { + foundMatch = + mTimedElement.SetAttr(aAttribute, aValue, aResult, *this, &rv); + } + + if (foundMatch) { + AnimationNeedsResample(); + if (NS_FAILED(rv)) { + ReportAttributeParseFailure(OwnerDoc(), aAttribute, aValue); + return false; + } + return true; + } + } + + return SVGAnimationElementBase::ParseAttribute( + aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult); +} + +void SVGAnimationElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, + bool aNotify) { + if (!aValue && aNamespaceID == kNameSpaceID_None) { + // Attribute is being removed. + if (AnimationFunction().UnsetAttr(aName) || + mTimedElement.UnsetAttr(aName)) { + AnimationNeedsResample(); + } + } + + SVGAnimationElementBase::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue, + aSubjectPrincipal, aNotify); + + if (SVGTests::IsConditionalProcessingAttribute(aName)) { + bool isDisabled = !SVGTests::PassesConditionalProcessingTests(); + if (mTimedElement.SetIsDisabled(isDisabled)) { + AnimationNeedsResample(); + } + } + + if (!IsInComposedDoc()) { + return; + } + + if (!((aNamespaceID == kNameSpaceID_None || + aNamespaceID == kNameSpaceID_XLink) && + aName == nsGkAtoms::href)) { + return; + } + + if (!aValue) { + if (aNamespaceID == kNameSpaceID_None) { + mHrefTarget.Unlink(); + AnimationTargetChanged(); + + // After unsetting href, we may still have xlink:href, so we + // should try to add it back. + const nsAttrValue* xlinkHref = + mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink); + if (xlinkHref) { + UpdateHrefTarget(xlinkHref->GetStringValue()); + } + } else if (!HasAttr(kNameSpaceID_None, nsGkAtoms::href)) { + mHrefTarget.Unlink(); + AnimationTargetChanged(); + } // else: we unset xlink:href, but we still have href attribute, so keep + // mHrefTarget linking to href. + } else if (!(aNamespaceID == kNameSpaceID_XLink && + HasAttr(kNameSpaceID_None, nsGkAtoms::href))) { + // Note: "href" takes priority over xlink:href. So if "xlink:href" is being + // set here, we only let that update our target if "href" is *unset*. + MOZ_ASSERT(aValue->Type() == nsAttrValue::eString, + "Expected href attribute to be string type"); + UpdateHrefTarget(aValue->GetStringValue()); + } // else: we're not yet in a document -- we'll update the target on + // next BindToTree call. +} + +//---------------------------------------------------------------------- +// SVG utility methods + +void SVGAnimationElement::ActivateByHyperlink() { + FlushAnimations(); + + // The behavior for when the target is an animation element is defined in + // SMIL Animation: + // http://www.w3.org/TR/smil-animation/#HyperlinkSemantics + SMILTimeValue seekTime = mTimedElement.GetHyperlinkTime(); + if (seekTime.IsDefinite()) { + SMILTimeContainer* timeContainer = GetTimeContainer(); + if (timeContainer) { + timeContainer->SetCurrentTime(seekTime.GetMillis()); + AnimationNeedsResample(); + // As with SVGSVGElement::SetCurrentTime, we need to trigger + // a synchronous sample now. + FlushAnimations(); + } + // else, silently fail. We mustn't be part of an SVG document fragment that + // is attached to the document tree so there's nothing we can do here + } else { + BeginElement(IgnoreErrors()); + } +} + +//---------------------------------------------------------------------- +// Implementation helpers + +SMILTimeContainer* SVGAnimationElement::GetTimeContainer() { + SVGSVGElement* element = SVGContentUtils::GetOuterSVGElement(this); + + if (element) { + return element->GetTimedDocumentRoot(); + } + + return nullptr; +} + +void SVGAnimationElement::BeginElementAt(float offset, ErrorResult& rv) { + // Make sure the timegraph is up-to-date + FlushAnimations(); + + // This will fail if we're not attached to a time container (SVG document + // fragment). + rv = mTimedElement.BeginElementAt(offset); + if (rv.Failed()) return; + + AnimationNeedsResample(); + // Force synchronous sample so that events resulting from this call arrive in + // the expected order and we get an up-to-date paint. + FlushAnimations(); +} + +void SVGAnimationElement::EndElementAt(float offset, ErrorResult& rv) { + // Make sure the timegraph is up-to-date + FlushAnimations(); + + rv = mTimedElement.EndElementAt(offset); + if (rv.Failed()) return; + + AnimationNeedsResample(); + // Force synchronous sample + FlushAnimations(); +} + +bool SVGAnimationElement::IsEventAttributeNameInternal(nsAtom* aName) { + return nsContentUtils::IsEventAttributeName(aName, EventNameType_SMIL); +} + +void SVGAnimationElement::UpdateHrefTarget(const nsAString& aHrefStr) { + nsCOMPtr baseURI = GetBaseURI(); + if (nsContentUtils::IsLocalRefURL(aHrefStr)) { + baseURI = SVGObserverUtils::GetBaseURLForLocalRef(this, baseURI); + } + nsCOMPtr targetURI; + nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), aHrefStr, + OwnerDoc(), baseURI); + mHrefTarget.ResetToURIFragmentID( + this, targetURI, OwnerDoc()->ReferrerInfoForInternalCSSAndSVGResources()); + AnimationTargetChanged(); +} + +void SVGAnimationElement::AnimationTargetChanged() { + mTimedElement.HandleTargetElementChange(GetTargetElementContent()); + AnimationNeedsResample(); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGAnimationElement.h b/dom/svg/SVGAnimationElement.h new file mode 100644 index 0000000000..7f41d623fd --- /dev/null +++ b/dom/svg/SVGAnimationElement.h @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGANIMATIONELEMENT_H_ +#define DOM_SVG_SVGANIMATIONELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILTimedElement.h" +#include "mozilla/dom/IDTracker.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGTests.h" + +namespace mozilla::dom { + +using SVGAnimationElementBase = SVGElement; + +class SVGAnimationElement : public SVGAnimationElementBase, public SVGTests { + protected: + explicit SVGAnimationElement( + already_AddRefed&& aNodeInfo); + nsresult Init(); + virtual ~SVGAnimationElement() = default; + + public: + // interfaces: + NS_DECL_ISUPPORTS_INHERITED + + NS_IMPL_FROMNODE_HELPER(SVGAnimationElement, IsSVGAnimationElement()) + + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SVGAnimationElement, + SVGAnimationElementBase) + + bool IsSVGAnimationElement() const final { return true; } + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override = 0; + + // nsIContent specializations + nsresult BindToTree(BindContext&, nsINode& aParent) override; + void UnbindFromTree(bool aNullParent) override; + + // Element specializations + bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) override; + void AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, bool aNotify) override; + + Element* GetTargetElementContent(); + virtual bool GetTargetAttributeName(int32_t* aNamespaceID, + nsAtom** aLocalName) const; + mozilla::SMILTimedElement& TimedElement(); + mozilla::SMILTimeContainer* GetTimeContainer(); + virtual SMILAnimationFunction& AnimationFunction() = 0; + + bool IsEventAttributeNameInternal(nsAtom* aName) override; + + // Utility methods for within SVG + void ActivateByHyperlink(); + + // WebIDL + SVGElement* GetTargetElement(); + float GetStartTime(ErrorResult& rv); + float GetCurrentTimeAsFloat(); + float GetSimpleDuration(ErrorResult& rv); + void BeginElement(ErrorResult& rv) { BeginElementAt(0.f, rv); } + void BeginElementAt(float offset, ErrorResult& rv); + void EndElement(ErrorResult& rv) { EndElementAt(0.f, rv); } + void EndElementAt(float offset, ErrorResult& rv); + + // SVGTests + SVGElement* AsSVGElement() final { return this; } + + protected: + // SVGElement overrides + + void UpdateHrefTarget(const nsAString& aHrefStr); + void AnimationTargetChanged(); + + /** + * Helper that provides a reference to the element with the ID that is + * referenced by the animation element's 'href' attribute, if any, and that + * will notify the animation element if the element that that ID identifies + * changes to a different element (or none). (If the 'href' attribute is not + * specified, then the animation target is the parent element and this helper + * is not used.) + */ + class HrefTargetTracker final : public IDTracker { + public: + explicit HrefTargetTracker(SVGAnimationElement* aAnimationElement) + : mAnimationElement(aAnimationElement) {} + + protected: + // We need to be notified when target changes, in order to request a + // sample (which will clear animation effects from old target and apply + // them to the new target) and update any event registrations. + void ElementChanged(Element* aFrom, Element* aTo) override { + IDTracker::ElementChanged(aFrom, aTo); + mAnimationElement->AnimationTargetChanged(); + } + + // We need to override IsPersistent to get persistent tracking (beyond the + // first time the target changes) + bool IsPersistent() override { return true; } + + private: + SVGAnimationElement* const mAnimationElement; + }; + + HrefTargetTracker mHrefTarget; + mozilla::SMILTimedElement mTimedElement; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGANIMATIONELEMENT_H_ diff --git a/dom/svg/SVGAttrTearoffTable.h b/dom/svg/SVGAttrTearoffTable.h new file mode 100644 index 0000000000..14c5d07466 --- /dev/null +++ b/dom/svg/SVGAttrTearoffTable.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGATTRTEAROFFTABLE_H_ +#define DOM_SVG_SVGATTRTEAROFFTABLE_H_ + +#include "mozilla/DebugOnly.h" +#include "mozilla/StaticPtr.h" +#include "nsTHashMap.h" +#include "nsDebug.h" +#include "nsHashKeys.h" + +namespace mozilla { + +/** + * Global hashmap to associate internal SVG data types (e.g. SVGAnimatedLength) + * with DOM tear-off objects (e.g. DOMSVGLength). This allows us to always + * return the same object for subsequent requests for DOM objects. + * + * We don't keep an owning reference to the tear-off objects so they are + * responsible for removing themselves from this table when they die. + */ +template +class SVGAttrTearoffTable { + public: +#ifdef DEBUG + ~SVGAttrTearoffTable() { + NS_ASSERTION(!mTable, "Tear-off objects remain in hashtable at shutdown."); + } +#endif + + TearoffType* GetTearoff(SimpleType* aSimple); + + void AddTearoff(SimpleType* aSimple, TearoffType* aTearoff); + + void RemoveTearoff(SimpleType* aSimple); + + private: + using SimpleTypePtrKey = nsPtrHashKey; + using TearoffTable = nsTHashMap; + + StaticAutoPtr mTable; +}; + +template +TearoffType* SVGAttrTearoffTable::GetTearoff( + SimpleType* aSimple) { + if (!mTable) { + return nullptr; + } + + TearoffType* tearoff = nullptr; + + DebugOnly found = mTable->Get(aSimple, &tearoff); + MOZ_ASSERT(!found || tearoff, + "null pointer stored in attribute tear-off map"); + + return tearoff; +} + +template +void SVGAttrTearoffTable::AddTearoff( + SimpleType* aSimple, TearoffType* aTearoff) { + if (!mTable) { + mTable = new TearoffTable(); + } + + // We shouldn't be adding a tear-off if there already is one. If that happens, + // something is wrong. + if (mTable->Get(aSimple, nullptr)) { + MOZ_ASSERT(false, "There is already a tear-off for this object."); + return; + } + + mTable->InsertOrUpdate(aSimple, aTearoff); +} + +template +void SVGAttrTearoffTable::RemoveTearoff( + SimpleType* aSimple) { + if (!mTable) { + // Perhaps something happened in between creating the SimpleType object and + // registering it + return; + } + + mTable->Remove(aSimple); + if (mTable->Count() == 0) { + mTable = nullptr; + } +} + +} // namespace mozilla + +#endif // DOM_SVG_SVGATTRTEAROFFTABLE_H_ diff --git a/dom/svg/SVGAttrValueWrapper.cpp b/dom/svg/SVGAttrValueWrapper.cpp new file mode 100644 index 0000000000..4bd097931f --- /dev/null +++ b/dom/svg/SVGAttrValueWrapper.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGAttrValueWrapper.h" + +#include "SVGAnimatedIntegerPair.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedNumberPair.h" +#include "SVGAnimatedOrient.h" +#include "SVGAnimatedPreserveAspectRatio.h" +#include "SVGAnimatedViewBox.h" +#include "SVGLengthList.h" +#include "SVGNumberList.h" +#include "SVGPathData.h" +#include "SVGPointList.h" +#include "SVGStringList.h" +#include "SVGTransformList.h" + +namespace mozilla { + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGAnimatedOrient* aOrient, + nsAString& aResult) { + aOrient->GetBaseValueString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGAnimatedIntegerPair* aIntegerPair, + nsAString& aResult) { + aIntegerPair->GetBaseValueString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGAnimatedLength* aLength, + nsAString& aResult) { + aLength->GetBaseValueString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGLengthList* aLengthList, + nsAString& aResult) { + aLengthList->GetValueAsString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGNumberList* aNumberList, + nsAString& aResult) { + aNumberList->GetValueAsString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGAnimatedNumberPair* aNumberPair, + nsAString& aResult) { + aNumberPair->GetBaseValueString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGPathData* aPathData, + nsAString& aResult) { + aPathData->GetValueAsString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGPointList* aPointList, + nsAString& aResult) { + aPointList->GetValueAsString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString( + const SVGAnimatedPreserveAspectRatio* aPreserveAspectRatio, + nsAString& aResult) { + aPreserveAspectRatio->GetBaseValueString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGStringList* aStringList, + nsAString& aResult) { + aStringList->GetValue(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGTransformList* aTransformList, + nsAString& aResult) { + aTransformList->GetValueAsString(aResult); +} + +/*static*/ +void SVGAttrValueWrapper::ToString(const SVGAnimatedViewBox* aViewBox, + nsAString& aResult) { + aViewBox->GetBaseValueString(aResult); +} + +} // namespace mozilla diff --git a/dom/svg/SVGAttrValueWrapper.h b/dom/svg/SVGAttrValueWrapper.h new file mode 100644 index 0000000000..c3fb96ba38 --- /dev/null +++ b/dom/svg/SVGAttrValueWrapper.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGATTRVALUEWRAPPER_H_ +#define DOM_SVG_SVGATTRVALUEWRAPPER_H_ + +/** + * Utility wrapper for handling SVG types used inside nsAttrValue so that these + * types don't need to be exported outside the SVG module. + */ + +#include "nsString.h" + +namespace mozilla { +class SVGAnimatedIntegerPair; +class SVGAnimatedLength; +class SVGAnimatedNumberPair; +class SVGAnimatedOrient; +class SVGAnimatedPreserveAspectRatio; +class SVGAnimatedViewBox; +class SVGLengthList; +class SVGNumberList; +class SVGPathData; +class SVGPointList; +class SVGStringList; +class SVGTransformList; + +class SVGAttrValueWrapper { + public: + static void ToString(const SVGAnimatedIntegerPair* aIntegerPair, + nsAString& aResult); + static void ToString(const SVGAnimatedLength* aLength, nsAString& aResult); + static void ToString(const SVGAnimatedNumberPair* aNumberPair, + nsAString& aResult); + static void ToString(const SVGAnimatedOrient* aOrient, nsAString& aResult); + static void ToString( + const SVGAnimatedPreserveAspectRatio* aPreserveAspectRatio, + nsAString& aResult); + static void ToString(const SVGAnimatedViewBox* aViewBox, nsAString& aResult); + static void ToString(const SVGLengthList* aLengthList, nsAString& aResult); + static void ToString(const SVGNumberList* aNumberList, nsAString& aResult); + static void ToString(const SVGPathData* aPathData, nsAString& aResult); + static void ToString(const SVGPointList* aPointList, nsAString& aResult); + static void ToString(const SVGStringList* aStringList, nsAString& aResult); + static void ToString(const SVGTransformList* aTransformList, + nsAString& aResult); +}; + +} /* namespace mozilla */ + +#endif // DOM_SVG_SVGATTRVALUEWRAPPER_H_ diff --git a/dom/svg/SVGCircleElement.cpp b/dom/svg/SVGCircleElement.cpp new file mode 100644 index 0000000000..c3f4beb40d --- /dev/null +++ b/dom/svg/SVGCircleElement.cpp @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ComputedStyle.h" +#include "mozilla/dom/SVGCircleElement.h" +#include "mozilla/gfx/2D.h" +#include "nsGkAtoms.h" +#include "mozilla/dom/SVGCircleElementBinding.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "SVGGeometryProperty.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Circle) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGCircleElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGCircleElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGCircleElement::sLengthInfo[3] = { + {nsGkAtoms::cx, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::cy, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::r, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::XY}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGCircleElement::SVGCircleElement( + already_AddRefed&& aNodeInfo) + : SVGCircleElementBase(std::move(aNodeInfo)) {} + +bool SVGCircleElement::IsAttributeMapped(const nsAtom* aAttribute) const { + return IsInLengthInfo(aAttribute, sLengthInfo) || + SVGCircleElementBase::IsAttributeMapped(aAttribute); +} + +namespace SVGT = SVGGeometryProperty::Tags; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGCircleElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGCircleElement::Cx() { + return mLengthAttributes[ATTR_CX].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGCircleElement::Cy() { + return mLengthAttributes[ATTR_CY].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGCircleElement::R() { + return mLengthAttributes[ATTR_R].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +bool SVGCircleElement::HasValidDimensions() const { + float r; + + if (SVGGeometryProperty::ResolveAll(this, &r)) { + return r > 0; + } + // This function might be called for an element in display:none subtree + // (e.g. SMIL animateMotion), we fall back to use SVG attributes. + return mLengthAttributes[ATTR_R].IsExplicitlySet() && + mLengthAttributes[ATTR_R].GetAnimValInSpecifiedUnits() > 0; +} + +SVGElement::LengthAttributesInfo SVGCircleElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +bool SVGCircleElement::GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, const Matrix* aToNonScalingStrokeSpace) { + float x, y, r; + + DebugOnly ok = + SVGGeometryProperty::ResolveAll(this, &x, &y, + &r); + MOZ_ASSERT(ok, "SVGGeometryProperty::ResolveAll failed"); + + if (r <= 0.f) { + // Rendering of the element is disabled + *aBounds = Rect(aToBoundsSpace.TransformPoint(Point(x, y)), Size()); + return true; + } + + if (aToBoundsSpace.IsRectilinear()) { + // Optimize the case where we can treat the circle as a rectangle and + // still get tight bounds. + if (aStrokeOptions.mLineWidth > 0.f) { + if (aToNonScalingStrokeSpace) { + if (aToNonScalingStrokeSpace->IsRectilinear()) { + MOZ_ASSERT(!aToNonScalingStrokeSpace->IsSingular()); + Rect userBounds(x - r, y - r, 2 * r, 2 * r); + SVGContentUtils::RectilinearGetStrokeBounds( + userBounds, aToBoundsSpace, *aToNonScalingStrokeSpace, + aStrokeOptions.mLineWidth, aBounds); + return true; + } + return false; + } + r += aStrokeOptions.mLineWidth / 2.f; + } + Rect rect(x - r, y - r, 2 * r, 2 * r); + *aBounds = aToBoundsSpace.TransformBounds(rect); + return true; + } + + return false; +} + +already_AddRefed SVGCircleElement::BuildPath(PathBuilder* aBuilder) { + float x, y, r; + if (!SVGGeometryProperty::ResolveAll(this, &x, + &y, &r)) { + // This function might be called for element in display:none subtree + // (e.g. getTotalLength), we fall back to use SVG attributes. + GetAnimatedLengthValues(&x, &y, &r, nullptr); + } + + if (r <= 0.0f) { + return nullptr; + } + + aBuilder->Arc(Point(x, y), r, 0, Float(2 * M_PI)); + + return aBuilder->Finish(); +} + +bool SVGCircleElement::IsLengthChangedViaCSS(const ComputedStyle& aNewStyle, + const ComputedStyle& aOldStyle) { + const auto& newSVGReset = *aNewStyle.StyleSVGReset(); + const auto& oldSVGReset = *aOldStyle.StyleSVGReset(); + + return newSVGReset.mCx != oldSVGReset.mCx || + newSVGReset.mCy != oldSVGReset.mCy || newSVGReset.mR != oldSVGReset.mR; +} + +nsCSSPropertyID SVGCircleElement::GetCSSPropertyIdForAttrEnum( + uint8_t aAttrEnum) { + switch (aAttrEnum) { + case ATTR_CX: + return eCSSProperty_cx; + case ATTR_CY: + return eCSSProperty_cy; + case ATTR_R: + return eCSSProperty_r; + default: + MOZ_ASSERT_UNREACHABLE("Unknown attr enum"); + return eCSSProperty_UNKNOWN; + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGCircleElement.h b/dom/svg/SVGCircleElement.h new file mode 100644 index 0000000000..85c5d0bd9d --- /dev/null +++ b/dom/svg/SVGCircleElement.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGCIRCLEELEMENT_H_ +#define DOM_SVG_SVGCIRCLEELEMENT_H_ + +#include "nsCSSPropertyID.h" +#include "SVGGeometryElement.h" +#include "SVGAnimatedLength.h" + +nsresult NS_NewSVGCircleElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla { +class ComputedStyle; + +namespace dom { + +using SVGCircleElementBase = SVGGeometryElement; + +class SVGCircleElement final : public SVGCircleElementBase { + protected: + explicit SVGCircleElement( + already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + friend nsresult(::NS_NewSVGCircleElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + public: + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + // SVGGeometryElement methods: + bool GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; + already_AddRefed BuildPath(PathBuilder* aBuilder) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + static bool IsLengthChangedViaCSS(const ComputedStyle& aNewStyle, + const ComputedStyle& aOldStyle); + static nsCSSPropertyID GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum); + + // WebIDL + already_AddRefed Cx(); + already_AddRefed Cy(); + already_AddRefed R(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + + enum { ATTR_CX, ATTR_CY, ATTR_R }; + SVGAnimatedLength mLengthAttributes[3]; + static LengthInfo sLengthInfo[3]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGCIRCLEELEMENT_H_ diff --git a/dom/svg/SVGClipPathElement.cpp b/dom/svg/SVGClipPathElement.cpp new file mode 100644 index 0000000000..a10624079d --- /dev/null +++ b/dom/svg/SVGClipPathElement.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGClipPathElement.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGClipPathElementBinding.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" +#include "nsGkAtoms.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(ClipPath) + +namespace mozilla::dom { + +using namespace SVGUnitTypes_Binding; + +JSObject* SVGClipPathElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGClipPathElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::EnumInfo SVGClipPathElement::sEnumInfo[1] = { + {nsGkAtoms::clipPathUnits, sSVGUnitTypesMap, SVG_UNIT_TYPE_USERSPACEONUSE}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGClipPathElement::SVGClipPathElement( + already_AddRefed&& aNodeInfo) + : SVGClipPathElementBase(std::move(aNodeInfo)) {} + +already_AddRefed +SVGClipPathElement::ClipPathUnits() { + return mEnumAttributes[CLIPPATHUNITS].ToDOMAnimatedEnum(this); +} + +SVGElement::EnumAttributesInfo SVGClipPathElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +bool SVGClipPathElement::IsUnitsObjectBoundingBox() const { + return mEnumAttributes[CLIPPATHUNITS].GetAnimValue() == + SVG_UNIT_TYPE_OBJECTBOUNDINGBOX; +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGClipPathElement) + +} // namespace mozilla::dom diff --git a/dom/svg/SVGClipPathElement.h b/dom/svg/SVGClipPathElement.h new file mode 100644 index 0000000000..7f00753a46 --- /dev/null +++ b/dom/svg/SVGClipPathElement.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGCLIPPATHELEMENT_H_ +#define DOM_SVG_SVGCLIPPATHELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "mozilla/dom/SVGTransformableElement.h" + +nsresult NS_NewSVGClipPathElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla { +class SVGClipPathFrame; + +namespace dom { + +using SVGClipPathElementBase = SVGTransformableElement; + +class SVGClipPathElement final : public SVGClipPathElementBase { + friend class mozilla::SVGClipPathFrame; + + protected: + friend nsresult(::NS_NewSVGClipPathElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGClipPathElement( + already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + + public: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed ClipPathUnits(); + + // This is an internal method that does not flush style, and thus + // the answer may be out of date if there's a pending style flush. + bool IsUnitsObjectBoundingBox() const; + + protected: + enum { CLIPPATHUNITS }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static EnumInfo sEnumInfo[1]; + + EnumAttributesInfo GetEnumInfo() override; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGCLIPPATHELEMENT_H_ diff --git a/dom/svg/SVGComponentTransferFunctionElement.h b/dom/svg/SVGComponentTransferFunctionElement.h new file mode 100644 index 0000000000..01e838d392 --- /dev/null +++ b/dom/svg/SVGComponentTransferFunctionElement.h @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGCOMPONENTTRANSFERFUNCTIONELEMENT_H_ +#define DOM_SVG_SVGCOMPONENTTRANSFERFUNCTIONELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedNumber.h" +#include "SVGAnimatedNumberList.h" +#include "mozilla/dom/SVGFilters.h" + +#define NS_SVG_FE_COMPONENT_TRANSFER_FUNCTION_ELEMENT_CID \ + { \ + 0xafab106d, 0xbc18, 0x4f7f, { \ + 0x9e, 0x29, 0xfe, 0xb4, 0xb0, 0x16, 0x5f, 0xf4 \ + } \ + } + +namespace mozilla::dom { + +class DOMSVGAnimatedNumberList; + +using SVGComponentTransferFunctionElementBase = SVGFEUnstyledElement; + +class SVGComponentTransferFunctionElement + : public SVGComponentTransferFunctionElementBase { + protected: + explicit SVGComponentTransferFunctionElement( + already_AddRefed&& aNodeInfo) + : SVGComponentTransferFunctionElementBase(std::move(aNodeInfo)) {} + + virtual ~SVGComponentTransferFunctionElement() = default; + + public: + using ComponentTransferAttributes = gfx::ComponentTransferAttributes; + + // interfaces: + NS_DECLARE_STATIC_IID_ACCESSOR( + NS_SVG_FE_COMPONENT_TRANSFER_FUNCTION_ELEMENT_CID) + + NS_DECL_ISUPPORTS_INHERITED + + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + + virtual int32_t GetChannel() = 0; + + void ComputeAttributes(int32_t aChannel, + ComponentTransferAttributes& aAttributes); + + // WebIDL + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override = 0; + already_AddRefed Type(); + already_AddRefed TableValues(); + already_AddRefed Slope(); + already_AddRefed Intercept(); + already_AddRefed Amplitude(); + already_AddRefed Exponent(); + already_AddRefed Offset(); + + protected: + NumberAttributesInfo GetNumberInfo() override; + EnumAttributesInfo GetEnumInfo() override; + NumberListAttributesInfo GetNumberListInfo() override; + + enum { TABLEVALUES }; + SVGAnimatedNumberList mNumberListAttributes[1]; + static NumberListInfo sNumberListInfo[1]; + + enum { SLOPE, INTERCEPT, AMPLITUDE, EXPONENT, OFFSET }; + SVGAnimatedNumber mNumberAttributes[5]; + static NumberInfo sNumberInfo[5]; + + enum { TYPE }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sTypeMap[]; + static EnumInfo sEnumInfo[1]; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(SVGComponentTransferFunctionElement, + NS_SVG_FE_COMPONENT_TRANSFER_FUNCTION_ELEMENT_CID) + +} // namespace mozilla::dom + +nsresult NS_NewSVGFEFuncRElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +class SVGFEFuncRElement : public SVGComponentTransferFunctionElement { + friend nsresult(::NS_NewSVGFEFuncRElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEFuncRElement( + already_AddRefed&& aNodeInfo) + : SVGComponentTransferFunctionElement(std::move(aNodeInfo)) {} + + public: + int32_t GetChannel() override { return 0; } + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; +}; + +} // namespace mozilla::dom + +nsresult NS_NewSVGFEFuncGElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +class SVGFEFuncGElement : public SVGComponentTransferFunctionElement { + friend nsresult(::NS_NewSVGFEFuncGElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEFuncGElement( + already_AddRefed&& aNodeInfo) + : SVGComponentTransferFunctionElement(std::move(aNodeInfo)) {} + + public: + int32_t GetChannel() override { return 1; } + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; +}; + +} // namespace mozilla::dom + +nsresult NS_NewSVGFEFuncBElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +class SVGFEFuncBElement : public SVGComponentTransferFunctionElement { + friend nsresult(::NS_NewSVGFEFuncBElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEFuncBElement( + already_AddRefed&& aNodeInfo) + : SVGComponentTransferFunctionElement(std::move(aNodeInfo)) {} + + public: + int32_t GetChannel() override { return 2; } + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; +}; + +} // namespace mozilla::dom + +nsresult NS_NewSVGFEFuncAElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +class SVGFEFuncAElement : public SVGComponentTransferFunctionElement { + friend nsresult(::NS_NewSVGFEFuncAElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEFuncAElement( + already_AddRefed&& aNodeInfo) + : SVGComponentTransferFunctionElement(std::move(aNodeInfo)) {} + + public: + int32_t GetChannel() override { return 3; } + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGCOMPONENTTRANSFERFUNCTIONELEMENT_H_ diff --git a/dom/svg/SVGContentUtils.cpp b/dom/svg/SVGContentUtils.cpp new file mode 100644 index 0000000000..0913ae48c5 --- /dev/null +++ b/dom/svg/SVGContentUtils.cpp @@ -0,0 +1,859 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Main header first: +// This is also necessary to ensure our definition of M_SQRT1_2 is picked up +#include "SVGContentUtils.h" + +// Keep others in (case-insensitive) order: +#include "gfx2DGlue.h" +#include "gfxMatrix.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/PresShell.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SVGContextPaint.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/TextUtils.h" +#include "nsComputedDOMStyle.h" +#include "nsContainerFrame.h" +#include "nsFontMetrics.h" +#include "nsIFrame.h" +#include "nsIScriptError.h" +#include "nsLayoutUtils.h" +#include "nsMathUtils.h" +#include "nsWhitespaceTokenizer.h" +#include "SVGAnimatedPreserveAspectRatio.h" +#include "SVGGeometryProperty.h" +#include "nsContentUtils.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/ComputedStyle.h" +#include "SVGPathDataParser.h" +#include "SVGPathData.h" +#include "SVGPathElement.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::dom::SVGPreserveAspectRatio_Binding; +using namespace mozilla::gfx; + +static bool ParseNumber(RangedPtr& aIter, + const RangedPtr& aEnd, double& aValue) { + int32_t sign; + if (!SVGContentUtils::ParseOptionalSign(aIter, aEnd, sign)) { + return false; + } + + // Absolute value of the integer part of the mantissa. + double intPart = 0.0; + + bool gotDot = *aIter == '.'; + + if (!gotDot) { + if (!mozilla::IsAsciiDigit(*aIter)) { + return false; + } + do { + intPart = 10.0 * intPart + mozilla::AsciiAlphanumericToNumber(*aIter); + ++aIter; + } while (aIter != aEnd && mozilla::IsAsciiDigit(*aIter)); + + if (aIter != aEnd) { + gotDot = *aIter == '.'; + } + } + + // Fractional part of the mantissa. + double fracPart = 0.0; + + if (gotDot) { + ++aIter; + if (aIter == aEnd || !mozilla::IsAsciiDigit(*aIter)) { + return false; + } + + // Power of ten by which we need to divide the fraction + double divisor = 1.0; + + do { + fracPart = 10.0 * fracPart + mozilla::AsciiAlphanumericToNumber(*aIter); + divisor *= 10.0; + ++aIter; + } while (aIter != aEnd && mozilla::IsAsciiDigit(*aIter)); + + fracPart /= divisor; + } + + bool gotE = false; + int32_t exponent = 0; + int32_t expSign; + + if (aIter != aEnd && (*aIter == 'e' || *aIter == 'E')) { + RangedPtr expIter(aIter); + + ++expIter; + if (expIter != aEnd) { + expSign = *expIter == '-' ? -1 : 1; + if (*expIter == '-' || *expIter == '+') { + ++expIter; + } + if (expIter != aEnd && mozilla::IsAsciiDigit(*expIter)) { + // At this point we're sure this is an exponent + // and not the start of a unit such as em or ex. + gotE = true; + } + } + + if (gotE) { + aIter = expIter; + do { + exponent = 10.0 * exponent + mozilla::AsciiAlphanumericToNumber(*aIter); + ++aIter; + } while (aIter != aEnd && mozilla::IsAsciiDigit(*aIter)); + } + } + + // Assemble the number + aValue = sign * (intPart + fracPart); + if (gotE) { + aValue *= pow(10.0, expSign * exponent); + } + return true; +} + +namespace mozilla { + +SVGSVGElement* SVGContentUtils::GetOuterSVGElement(SVGElement* aSVGElement) { + Element* element = nullptr; + Element* ancestor = aSVGElement->GetParentElementCrossingShadowRoot(); + + while (ancestor && ancestor->IsSVGElement() && + !ancestor->IsSVGElement(nsGkAtoms::foreignObject)) { + element = ancestor; + ancestor = element->GetParentElementCrossingShadowRoot(); + } + + return SVGSVGElement::FromNodeOrNull(element); +} + +enum DashState { + eDashedStroke, + eContinuousStroke, //< all dashes, no gaps + eNoStroke //< all gaps, no dashes +}; + +static DashState GetStrokeDashData( + SVGContentUtils::AutoStrokeOptions* aStrokeOptions, SVGElement* aElement, + const nsStyleSVG* aStyleSVG, const SVGContextPaint* aContextPaint) { + size_t dashArrayLength; + Float totalLengthOfDashes = 0.0, totalLengthOfGaps = 0.0; + Float pathScale = 1.0; + + if (aStyleSVG->mStrokeDasharray.IsContextValue()) { + if (!aContextPaint) { + return eContinuousStroke; + } + const FallibleTArray& dashSrc = aContextPaint->GetStrokeDashArray(); + dashArrayLength = dashSrc.Length(); + if (dashArrayLength <= 0) { + return eContinuousStroke; + } + Float* dashPattern = aStrokeOptions->InitDashPattern(dashArrayLength); + if (!dashPattern) { + return eContinuousStroke; + } + for (size_t i = 0; i < dashArrayLength; i++) { + if (dashSrc[i] < 0.0) { + return eContinuousStroke; // invalid + } + dashPattern[i] = Float(dashSrc[i]); + (i % 2 ? totalLengthOfGaps : totalLengthOfDashes) += dashSrc[i]; + } + } else { + const auto dasharray = aStyleSVG->mStrokeDasharray.AsValues().AsSpan(); + dashArrayLength = dasharray.Length(); + if (dashArrayLength <= 0) { + return eContinuousStroke; + } + if (auto* shapeElement = SVGGeometryElement::FromNode(aElement)) { + pathScale = + shapeElement->GetPathLengthScale(SVGGeometryElement::eForStroking); + if (pathScale <= 0 || !std::isfinite(pathScale)) { + return eContinuousStroke; + } + } + Float* dashPattern = aStrokeOptions->InitDashPattern(dashArrayLength); + if (!dashPattern) { + return eContinuousStroke; + } + for (uint32_t i = 0; i < dashArrayLength; i++) { + Float dashLength = + SVGContentUtils::CoordToFloat(aElement, dasharray[i]) * pathScale; + if (dashLength < 0.0) { + return eContinuousStroke; // invalid + } + dashPattern[i] = dashLength; + (i % 2 ? totalLengthOfGaps : totalLengthOfDashes) += dashLength; + } + } + + // Now that aStrokeOptions.mDashPattern is fully initialized (we didn't + // return early above) we can safely set mDashLength: + aStrokeOptions->mDashLength = dashArrayLength; + + if ((dashArrayLength % 2) == 1) { + // If we have a dash pattern with an odd number of lengths the pattern + // repeats a second time, per the SVG spec., and as implemented by Moz2D. + // When deciding whether to return eNoStroke or eContinuousStroke below we + // need to take into account that in the repeat pattern the dashes become + // gaps, and the gaps become dashes. + Float origTotalLengthOfDashes = totalLengthOfDashes; + totalLengthOfDashes += totalLengthOfGaps; + totalLengthOfGaps += origTotalLengthOfDashes; + } + + // Stroking using dashes is much slower than stroking a continuous line + // (see bug 609361 comment 40), and much, much slower than not stroking the + // line at all. Here we check for cases when the dash pattern causes the + // stroke to essentially be continuous or to be nonexistent in which case + // we can avoid expensive stroking operations (the underlying platform + // graphics libraries don't seem to optimize for this). + if (totalLengthOfGaps <= 0) { + return eContinuousStroke; + } + // We can only return eNoStroke if the value of stroke-linecap isn't + // adding caps to zero length dashes. + if (totalLengthOfDashes <= 0 && + aStyleSVG->mStrokeLinecap == StyleStrokeLinecap::Butt) { + return eNoStroke; + } + + if (aStyleSVG->mStrokeDashoffset.IsContextValue()) { + aStrokeOptions->mDashOffset = + Float(aContextPaint ? aContextPaint->GetStrokeDashOffset() : 0); + } else { + aStrokeOptions->mDashOffset = + SVGContentUtils::CoordToFloat( + aElement, aStyleSVG->mStrokeDashoffset.AsLengthPercentage()) * + pathScale; + } + + return eDashedStroke; +} + +void SVGContentUtils::GetStrokeOptions(AutoStrokeOptions* aStrokeOptions, + SVGElement* aElement, + const ComputedStyle* aComputedStyle, + const SVGContextPaint* aContextPaint, + StrokeOptionFlags aFlags) { + auto doCompute = [&](const ComputedStyle* computedStyle) { + const nsStyleSVG* styleSVG = computedStyle->StyleSVG(); + + bool checkedDashAndStrokeIsDashed = false; + if (aFlags != eIgnoreStrokeDashing) { + DashState dashState = + GetStrokeDashData(aStrokeOptions, aElement, styleSVG, aContextPaint); + + if (dashState == eNoStroke) { + // Hopefully this will shortcircuit any stroke operations: + aStrokeOptions->mLineWidth = 0; + return; + } + if (dashState == eContinuousStroke && aStrokeOptions->mDashPattern) { + // Prevent our caller from wasting time looking at a pattern without + // gaps: + aStrokeOptions->DiscardDashPattern(); + } + checkedDashAndStrokeIsDashed = (dashState == eDashedStroke); + } + + aStrokeOptions->mLineWidth = + GetStrokeWidth(aElement, computedStyle, aContextPaint); + + aStrokeOptions->mMiterLimit = Float(styleSVG->mStrokeMiterlimit); + + switch (styleSVG->mStrokeLinejoin) { + case StyleStrokeLinejoin::Miter: + aStrokeOptions->mLineJoin = JoinStyle::MITER_OR_BEVEL; + break; + case StyleStrokeLinejoin::Round: + aStrokeOptions->mLineJoin = JoinStyle::ROUND; + break; + case StyleStrokeLinejoin::Bevel: + aStrokeOptions->mLineJoin = JoinStyle::BEVEL; + break; + } + + if (ShapeTypeHasNoCorners(aElement) && !checkedDashAndStrokeIsDashed) { + // Note: if aFlags == eIgnoreStrokeDashing then we may be returning the + // wrong linecap value here, since the actual linecap used on render in + // this case depends on whether the stroke is dashed or not. + aStrokeOptions->mLineCap = CapStyle::BUTT; + } else { + switch (styleSVG->mStrokeLinecap) { + case StyleStrokeLinecap::Butt: + aStrokeOptions->mLineCap = CapStyle::BUTT; + break; + case StyleStrokeLinecap::Round: + aStrokeOptions->mLineCap = CapStyle::ROUND; + break; + case StyleStrokeLinecap::Square: + aStrokeOptions->mLineCap = CapStyle::SQUARE; + break; + } + } + }; + + if (aComputedStyle) { + doCompute(aComputedStyle); + } else { + SVGGeometryProperty::DoForComputedStyle(aElement, doCompute); + } +} + +Float SVGContentUtils::GetStrokeWidth(const SVGElement* aElement, + const ComputedStyle* aComputedStyle, + const SVGContextPaint* aContextPaint) { + Float res = 0.0; + + auto doCompute = [&](const ComputedStyle* computedStyle) { + const nsStyleSVG* styleSVG = computedStyle->StyleSVG(); + + if (styleSVG->mStrokeWidth.IsContextValue()) { + res = aContextPaint ? aContextPaint->GetStrokeWidth() : 1.0; + } else { + auto& lp = styleSVG->mStrokeWidth.AsLengthPercentage(); + if (lp.HasPercent() && aElement) { + auto counter = + aElement->IsSVGElement(nsGkAtoms::text) + ? UseCounter::eUseCounter_custom_PercentageStrokeWidthInSVGText + : UseCounter::eUseCounter_custom_PercentageStrokeWidthInSVG; + aElement->OwnerDoc()->SetUseCounter(counter); + } + res = SVGContentUtils::CoordToFloat(aElement, lp); + } + }; + + if (aComputedStyle) { + doCompute(aComputedStyle); + } else { + SVGGeometryProperty::DoForComputedStyle(aElement, doCompute); + } + + return res; +} + +float SVGContentUtils::GetFontSize(const Element* aElement) { + if (!aElement) { + return 1.0f; + } + + nsPresContext* pc = nsContentUtils::GetContextForContent(aElement); + if (!pc) { + return 1.0f; + } + + if (auto* f = aElement->GetPrimaryFrame()) { + return GetFontSize(f->Style(), pc); + } + + if (RefPtr style = + nsComputedDOMStyle::GetComputedStyleNoFlush(aElement)) { + return GetFontSize(style, pc); + } + + // ReportToConsole + NS_WARNING("Couldn't get ComputedStyle for content in GetFontStyle"); + return 1.0f; +} + +float SVGContentUtils::GetFontSize(const nsIFrame* aFrame) { + MOZ_ASSERT(aFrame, "NULL frame in GetFontSize"); + return GetFontSize(aFrame->Style(), aFrame->PresContext()); +} + +float SVGContentUtils::GetFontSize(const ComputedStyle* aComputedStyle, + nsPresContext* aPresContext) { + MOZ_ASSERT(aComputedStyle); + MOZ_ASSERT(aPresContext); + + return aComputedStyle->StyleFont()->mSize.ToCSSPixels() / + aPresContext->TextZoom(); +} + +float SVGContentUtils::GetFontXHeight(const Element* aElement) { + if (!aElement) { + return 1.0f; + } + + nsPresContext* pc = nsContentUtils::GetContextForContent(aElement); + if (!pc) { + return 1.0f; + } + + if (auto* f = aElement->GetPrimaryFrame()) { + return GetFontXHeight(f->Style(), pc); + } + + if (RefPtr style = + nsComputedDOMStyle::GetComputedStyleNoFlush(aElement)) { + return GetFontXHeight(style, pc); + } + + // ReportToConsole + NS_WARNING("Couldn't get ComputedStyle for content in GetFontStyle"); + return 1.0f; +} + +float SVGContentUtils::GetFontXHeight(const nsIFrame* aFrame) { + MOZ_ASSERT(aFrame, "NULL frame in GetFontXHeight"); + return GetFontXHeight(aFrame->Style(), aFrame->PresContext()); +} + +float SVGContentUtils::GetFontXHeight(const ComputedStyle* aComputedStyle, + nsPresContext* aPresContext) { + MOZ_ASSERT(aComputedStyle && aPresContext); + + RefPtr fontMetrics = + nsLayoutUtils::GetFontMetricsForComputedStyle(aComputedStyle, + aPresContext); + + if (!fontMetrics) { + // ReportToConsole + NS_WARNING("no FontMetrics in GetFontXHeight()"); + return 1.0f; + } + + nscoord xHeight = fontMetrics->XHeight(); + return nsPresContext::AppUnitsToFloatCSSPixels(xHeight) / + aPresContext->TextZoom(); +} +nsresult SVGContentUtils::ReportToConsole(const Document* doc, + const char* aWarning, + const nsTArray& aParams) { + return nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "SVG"_ns, + doc, nsContentUtils::eSVG_PROPERTIES, + aWarning, aParams); +} + +bool SVGContentUtils::EstablishesViewport(const nsIContent* aContent) { + // Although SVG 1.1 states that is an element that establishes a + // viewport, this is really only for the document it references, not + // for any child content, which is what this function is used for. + return aContent && + aContent->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::foreignObject, + nsGkAtoms::symbol); +} + +SVGViewportElement* SVGContentUtils::GetNearestViewportElement( + const nsIContent* aContent) { + nsIContent* element = aContent->GetFlattenedTreeParent(); + + while (element && element->IsSVGElement()) { + if (EstablishesViewport(element)) { + if (element->IsSVGElement(nsGkAtoms::foreignObject)) { + return nullptr; + } + MOZ_ASSERT(element->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol), + "upcoming static_cast is only valid for " + "SVGViewportElement subclasses"); + return static_cast(element); + } + element = element->GetFlattenedTreeParent(); + } + return nullptr; +} + +static gfx::Matrix GetCTMInternal(SVGElement* aElement, bool aScreenCTM, + bool aHaveRecursed) { + auto getLocalTransformHelper = + [](SVGElement const* e, bool shouldIncludeChildToUserSpace) -> gfxMatrix { + gfxMatrix ret; + + if (auto* f = e->GetPrimaryFrame()) { + ret = SVGUtils::GetTransformMatrixInUserSpace(f); + } else { + // FIXME: Ideally we should also return the correct matrix + // for display:none, but currently transform related code relies + // heavily on the present of a frame. + // For now we just fall back to |PrependLocalTransformsTo| which + // doesn't account for CSS transform. + ret = e->PrependLocalTransformsTo({}, eUserSpaceToParent); + } + + if (shouldIncludeChildToUserSpace) { + ret = e->PrependLocalTransformsTo({}, eChildToUserSpace) * ret; + } + + return ret; + }; + + gfxMatrix matrix = getLocalTransformHelper(aElement, aHaveRecursed); + + SVGElement* element = aElement; + nsIContent* ancestor = aElement->GetFlattenedTreeParent(); + + while (ancestor && ancestor->IsSVGElement() && + !ancestor->IsSVGElement(nsGkAtoms::foreignObject)) { + element = static_cast(ancestor); + matrix *= getLocalTransformHelper(element, true); + if (!aScreenCTM && SVGContentUtils::EstablishesViewport(element)) { + if (!element->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol)) { + NS_ERROR("New (SVG > 1.1) SVG viewport establishing element?"); + return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular + } + // XXX spec seems to say x,y translation should be undone for IsInnerSVG + return gfx::ToMatrix(matrix); + } + ancestor = ancestor->GetFlattenedTreeParent(); + } + if (!aScreenCTM) { + // didn't find a nearestViewportElement + return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular + } + if (!element->IsSVGElement(nsGkAtoms::svg)) { + // Not a valid SVG fragment + return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular + } + if (element == aElement && !aHaveRecursed) { + // We get here when getScreenCTM() is called on an outer-. + // Consistency with other elements would have us include only the + // eFromUserSpace transforms, but we include the eAllTransforms + // transforms in this case since that's what we've been doing for + // a while, and it keeps us consistent with WebKit and Opera (if not + // really with the ambiguous spec). + matrix = getLocalTransformHelper(aElement, true); + } + + if (auto* f = element->GetPrimaryFrame()) { + if (f->IsSVGOuterSVGFrame()) { + nsMargin bp = f->GetUsedBorderAndPadding(); + matrix.PostTranslate( + NSAppUnitsToFloatPixels(bp.left, AppUnitsPerCSSPixel()), + NSAppUnitsToFloatPixels(bp.top, AppUnitsPerCSSPixel())); + } + } + + if (!ancestor || !ancestor->IsElement()) { + return gfx::ToMatrix(matrix); + } + if (auto* ancestorSVG = SVGElement::FromNode(ancestor)) { + return gfx::ToMatrix(matrix) * GetCTMInternal(ancestorSVG, true, true); + } + + // XXX this does not take into account CSS transform, or that the non-SVG + // content that we've hit may itself be inside an SVG foreignObject higher up + Document* currentDoc = aElement->GetComposedDoc(); + float x = 0.0f, y = 0.0f; + if (currentDoc && element->IsSVGElement(nsGkAtoms::svg)) { + PresShell* presShell = currentDoc->GetPresShell(); + if (presShell) { + nsIFrame* frame = element->GetPrimaryFrame(); + nsIFrame* ancestorFrame = presShell->GetRootFrame(); + if (frame && ancestorFrame) { + nsPoint point = frame->GetOffsetTo(ancestorFrame); + x = nsPresContext::AppUnitsToFloatCSSPixels(point.x); + y = nsPresContext::AppUnitsToFloatCSSPixels(point.y); + } + } + } + return ToMatrix(matrix).PostTranslate(x, y); +} + +gfx::Matrix SVGContentUtils::GetCTM(SVGElement* aElement, bool aScreenCTM) { + return GetCTMInternal(aElement, aScreenCTM, false); +} + +void SVGContentUtils::RectilinearGetStrokeBounds( + const Rect& aRect, const Matrix& aToBoundsSpace, + const Matrix& aToNonScalingStrokeSpace, float aStrokeWidth, Rect* aBounds) { + MOZ_ASSERT(aToBoundsSpace.IsRectilinear(), + "aToBoundsSpace must be rectilinear"); + MOZ_ASSERT(aToNonScalingStrokeSpace.IsRectilinear(), + "aToNonScalingStrokeSpace must be rectilinear"); + + Matrix nonScalingToSource = aToNonScalingStrokeSpace.Inverse(); + Matrix nonScalingToBounds = nonScalingToSource * aToBoundsSpace; + + *aBounds = aToBoundsSpace.TransformBounds(aRect); + + // Compute the amounts dx and dy that nonScalingToBounds scales a half-width + // stroke in the x and y directions, and then inflate aBounds by those amounts + // so that when aBounds is transformed back to non-scaling-stroke space + // it will map onto the correct stroked bounds. + + Float dx = 0.0f; + Float dy = 0.0f; + // nonScalingToBounds is rectilinear, so either _12 and _21 are zero or _11 + // and _22 are zero, and in each case the non-zero entries (from among _11, + // _12, _21, _22) simply scale the stroke width in the x and y directions. + if (FuzzyEqual(nonScalingToBounds._12, 0) && + FuzzyEqual(nonScalingToBounds._21, 0)) { + dx = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._11); + dy = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._22); + } else { + dx = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._21); + dy = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._12); + } + + aBounds->Inflate(dx, dy); +} + +double SVGContentUtils::ComputeNormalizedHypotenuse(double aWidth, + double aHeight) { + return NS_hypot(aWidth, aHeight) / M_SQRT2; +} + +float SVGContentUtils::AngleBisect(float a1, float a2) { + float delta = std::fmod(a2 - a1, static_cast(2 * M_PI)); + if (delta < 0) { + delta += static_cast(2 * M_PI); + } + /* delta is now the angle from a1 around to a2, in the range [0, 2*M_PI) */ + float r = a1 + delta / 2; + if (delta >= M_PI) { + /* the arc from a2 to a1 is smaller, so use the ray on that side */ + r += static_cast(M_PI); + } + return r; +} + +gfx::Matrix SVGContentUtils::GetViewBoxTransform( + float aViewportWidth, float aViewportHeight, float aViewboxX, + float aViewboxY, float aViewboxWidth, float aViewboxHeight, + const SVGAnimatedPreserveAspectRatio& aPreserveAspectRatio) { + return GetViewBoxTransform(aViewportWidth, aViewportHeight, aViewboxX, + aViewboxY, aViewboxWidth, aViewboxHeight, + aPreserveAspectRatio.GetAnimValue()); +} + +gfx::Matrix SVGContentUtils::GetViewBoxTransform( + float aViewportWidth, float aViewportHeight, float aViewboxX, + float aViewboxY, float aViewboxWidth, float aViewboxHeight, + const SVGPreserveAspectRatio& aPreserveAspectRatio) { + NS_ASSERTION(aViewportWidth >= 0, "viewport width must be nonnegative!"); + NS_ASSERTION(aViewportHeight >= 0, "viewport height must be nonnegative!"); + NS_ASSERTION(aViewboxWidth > 0, "viewBox width must be greater than zero!"); + NS_ASSERTION(aViewboxHeight > 0, "viewBox height must be greater than zero!"); + + uint16_t align = aPreserveAspectRatio.GetAlign(); + uint16_t meetOrSlice = aPreserveAspectRatio.GetMeetOrSlice(); + + // default to the defaults + if (align == SVG_PRESERVEASPECTRATIO_UNKNOWN) + align = SVG_PRESERVEASPECTRATIO_XMIDYMID; + if (meetOrSlice == SVG_MEETORSLICE_UNKNOWN) + meetOrSlice = SVG_MEETORSLICE_MEET; + + float a, d, e, f; + a = aViewportWidth / aViewboxWidth; + d = aViewportHeight / aViewboxHeight; + e = 0.0f; + f = 0.0f; + + if (align != SVG_PRESERVEASPECTRATIO_NONE && a != d) { + if ((meetOrSlice == SVG_MEETORSLICE_MEET && a < d) || + (meetOrSlice == SVG_MEETORSLICE_SLICE && d < a)) { + d = a; + switch (align) { + case SVG_PRESERVEASPECTRATIO_XMINYMIN: + case SVG_PRESERVEASPECTRATIO_XMIDYMIN: + case SVG_PRESERVEASPECTRATIO_XMAXYMIN: + break; + case SVG_PRESERVEASPECTRATIO_XMINYMID: + case SVG_PRESERVEASPECTRATIO_XMIDYMID: + case SVG_PRESERVEASPECTRATIO_XMAXYMID: + f = (aViewportHeight - a * aViewboxHeight) / 2.0f; + break; + case SVG_PRESERVEASPECTRATIO_XMINYMAX: + case SVG_PRESERVEASPECTRATIO_XMIDYMAX: + case SVG_PRESERVEASPECTRATIO_XMAXYMAX: + f = aViewportHeight - a * aViewboxHeight; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown value for align"); + } + } else if ((meetOrSlice == SVG_MEETORSLICE_MEET && d < a) || + (meetOrSlice == SVG_MEETORSLICE_SLICE && a < d)) { + a = d; + switch (align) { + case SVG_PRESERVEASPECTRATIO_XMINYMIN: + case SVG_PRESERVEASPECTRATIO_XMINYMID: + case SVG_PRESERVEASPECTRATIO_XMINYMAX: + break; + case SVG_PRESERVEASPECTRATIO_XMIDYMIN: + case SVG_PRESERVEASPECTRATIO_XMIDYMID: + case SVG_PRESERVEASPECTRATIO_XMIDYMAX: + e = (aViewportWidth - a * aViewboxWidth) / 2.0f; + break; + case SVG_PRESERVEASPECTRATIO_XMAXYMIN: + case SVG_PRESERVEASPECTRATIO_XMAXYMID: + case SVG_PRESERVEASPECTRATIO_XMAXYMAX: + e = aViewportWidth - a * aViewboxWidth; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown value for align"); + } + } else + MOZ_ASSERT_UNREACHABLE("Unknown value for meetOrSlice"); + } + + if (aViewboxX) e += -a * aViewboxX; + if (aViewboxY) f += -d * aViewboxY; + + return gfx::Matrix(a, 0.0f, 0.0f, d, e, f); +} + +template +bool SVGContentUtils::ParseNumber(RangedPtr& aIter, + const RangedPtr& aEnd, + floatType& aValue) { + RangedPtr iter(aIter); + + double value; + if (!::ParseNumber(iter, aEnd, value)) { + return false; + } + floatType floatValue = floatType(value); + if (!std::isfinite(floatValue)) { + return false; + } + aValue = floatValue; + aIter = iter; + return true; +} + +template bool SVGContentUtils::ParseNumber( + RangedPtr& aIter, const RangedPtr& aEnd, + float& aValue); + +template bool SVGContentUtils::ParseNumber( + RangedPtr& aIter, const RangedPtr& aEnd, + double& aValue); + +RangedPtr SVGContentUtils::GetStartRangedPtr( + const nsAString& aString) { + return RangedPtr(aString.Data(), aString.Length()); +} + +RangedPtr SVGContentUtils::GetEndRangedPtr( + const nsAString& aString) { + return RangedPtr(aString.Data() + aString.Length(), + aString.Data(), aString.Length()); +} + +template +bool SVGContentUtils::ParseNumber(const nsAString& aString, floatType& aValue) { + RangedPtr iter = GetStartRangedPtr(aString); + const RangedPtr end = GetEndRangedPtr(aString); + + return ParseNumber(iter, end, aValue) && iter == end; +} + +template bool SVGContentUtils::ParseNumber(const nsAString& aString, + float& aValue); +template bool SVGContentUtils::ParseNumber(const nsAString& aString, + double& aValue); + +/* static */ +bool SVGContentUtils::ParseInteger(RangedPtr& aIter, + const RangedPtr& aEnd, + int32_t& aValue) { + RangedPtr iter(aIter); + + int32_t sign; + if (!ParseOptionalSign(iter, aEnd, sign)) { + return false; + } + + if (!mozilla::IsAsciiDigit(*iter)) { + return false; + } + + int64_t value = 0; + + do { + if (value <= std::numeric_limits::max()) { + value = 10 * value + mozilla::AsciiAlphanumericToNumber(*iter); + } + ++iter; + } while (iter != aEnd && mozilla::IsAsciiDigit(*iter)); + + aIter = iter; + aValue = int32_t(clamped(sign * value, + int64_t(std::numeric_limits::min()), + int64_t(std::numeric_limits::max()))); + return true; +} + +/* static */ +bool SVGContentUtils::ParseInteger(const nsAString& aString, int32_t& aValue) { + RangedPtr iter = GetStartRangedPtr(aString); + const RangedPtr end = GetEndRangedPtr(aString); + + return ParseInteger(iter, end, aValue) && iter == end; +} + +float SVGContentUtils::CoordToFloat(const SVGElement* aContent, + const LengthPercentage& aLength, + uint8_t aCtxType) { + float result = aLength.ResolveToCSSPixelsWith([&] { + SVGViewportElement* ctx = aContent->GetCtx(); + return CSSCoord(ctx ? ctx->GetLength(aCtxType) : 0.0f); + }); + if (aLength.IsCalc()) { + const auto& calc = aLength.AsCalc(); + if (calc.clamping_mode == StyleAllowedNumericType::NonNegative) { + result = std::max(result, 0.0f); + } else { + MOZ_ASSERT(calc.clamping_mode == StyleAllowedNumericType::All); + } + } + return result; +} + +already_AddRefed SVGContentUtils::GetPath( + const nsAString& aPathString) { + SVGPathData pathData; + SVGPathDataParser parser(aPathString, &pathData); + if (!parser.Parse()) { + return nullptr; + } + + RefPtr drawTarget = + gfxPlatform::ThreadLocalScreenReferenceDrawTarget(); + RefPtr builder = + drawTarget->CreatePathBuilder(FillRule::FILL_WINDING); + + return pathData.BuildPath(builder, StyleStrokeLinecap::Butt, 1); +} + +bool SVGContentUtils::ShapeTypeHasNoCorners(const nsIContent* aContent) { + return aContent && + aContent->IsAnyOfSVGElements(nsGkAtoms::circle, nsGkAtoms::ellipse); +} + +nsDependentSubstring SVGContentUtils::GetAndEnsureOneToken( + const nsAString& aString, bool& aSuccess) { + nsWhitespaceTokenizerTemplate tokenizer( + aString); + + aSuccess = false; + if (!tokenizer.hasMoreTokens()) { + return {}; + } + auto token = tokenizer.nextToken(); + if (tokenizer.hasMoreTokens()) { + return {}; + } + + aSuccess = true; + return token; +} + +} // namespace mozilla diff --git a/dom/svg/SVGContentUtils.h b/dom/svg/SVGContentUtils.h new file mode 100644 index 0000000000..8cd017b709 --- /dev/null +++ b/dom/svg/SVGContentUtils.h @@ -0,0 +1,349 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGCONTENTUTILS_H_ +#define DOM_SVG_SVGCONTENTUTILS_H_ + +// include math.h to pick up definition of M_ maths defines e.g. M_PI +#include + +#include "mozilla/gfx/2D.h" // for StrokeOptions +#include "mozilla/gfx/Matrix.h" +#include "mozilla/RangedPtr.h" +#include "nsError.h" +#include "nsStringFwd.h" +#include "nsTArray.h" +#include "gfx2DGlue.h" +#include "nsDependentSubstring.h" + +class nsIContent; + +class nsIFrame; +class nsPresContext; + +namespace mozilla { +class ComputedStyle; +class SVGAnimatedTransformList; +class SVGAnimatedPreserveAspectRatio; +class SVGContextPaint; +class SVGPreserveAspectRatio; +union StyleLengthPercentageUnion; +namespace dom { +class Document; +class Element; +class SVGElement; +class SVGSVGElement; +class SVGViewportElement; +} // namespace dom + +#define SVG_ZERO_LENGTH_PATH_FIX_FACTOR 512 + +/** + * SVGTransformTypes controls the transforms that PrependLocalTransformsTo + * applies. + * + * If aWhich is eAllTransforms, then all the transforms from the coordinate + * space established by this element for its children to the coordinate + * space established by this element's parent element for this element, are + * included. + * + * If aWhich is eUserSpaceToParent, then only the transforms from this + * element's userspace to the coordinate space established by its parent is + * included. This includes any transforms introduced by the 'transform' + * attribute, transform animations and animateMotion, but not any offsets + * due to e.g. 'x'/'y' attributes, or any transform due to a 'viewBox' + * attribute. (SVG userspace is defined to be the coordinate space in which + * coordinates on an element apply.) + * + * If aWhich is eChildToUserSpace, then only the transforms from the + * coordinate space established by this element for its childre to this + * elements userspace are included. This includes any offsets due to e.g. + * 'x'/'y' attributes, and any transform due to a 'viewBox' attribute, but + * does not include any transforms due to the 'transform' attribute. + */ +enum SVGTransformTypes { + eAllTransforms, + eUserSpaceToParent, + eChildToUserSpace +}; + +/** + * Functions generally used by SVG Content classes. Functions here + * should not generally depend on layout methods/classes e.g. SVGUtils + */ +class SVGContentUtils { + public: + using Float = gfx::Float; + using Matrix = gfx::Matrix; + using Rect = gfx::Rect; + using StrokeOptions = gfx::StrokeOptions; + + /* + * Get the outer SVG element of an nsIContent + */ + static dom::SVGSVGElement* GetOuterSVGElement(dom::SVGElement* aSVGElement); + + /** + * Moz2D's StrokeOptions requires someone else to own its mDashPattern + * buffer, which is a pain when you want to initialize a StrokeOptions object + * in a helper function and pass it out. This sub-class owns the mDashPattern + * buffer so that consumers of such a helper function don't need to worry + * about creating it, passing it in, or deleting it. (An added benefit is + * that in the typical case when stroke-dasharray is short it will avoid + * allocating.) + */ + struct AutoStrokeOptions : public StrokeOptions { + AutoStrokeOptions() { + MOZ_ASSERT(mDashLength == 0, "InitDashPattern() depends on this"); + } + ~AutoStrokeOptions() { + if (mDashPattern && mDashPattern != mSmallArray) { + delete[] mDashPattern; + } + } + /** + * Creates the buffer to store the stroke-dasharray, assuming out-of-memory + * does not occur. The buffer's address is assigned to mDashPattern and + * returned to the caller as a non-const pointer (so that the caller can + * initialize the values in the buffer, since mDashPattern is const). + */ + Float* InitDashPattern(size_t aDashCount) { + if (aDashCount <= MOZ_ARRAY_LENGTH(mSmallArray)) { + mDashPattern = mSmallArray; + return mSmallArray; + } + Float* nonConstArray = new (mozilla::fallible) Float[aDashCount]; + mDashPattern = nonConstArray; + return nonConstArray; + } + void DiscardDashPattern() { + if (mDashPattern && mDashPattern != mSmallArray) { + delete[] mDashPattern; + } + mDashLength = 0; + mDashPattern = nullptr; + } + + private: + // Most dasharrays will fit in this and save us allocating + Float mSmallArray[16]; + }; + + enum StrokeOptionFlags { eAllStrokeOptions, eIgnoreStrokeDashing }; + /** + * Note: the linecap style returned in aStrokeOptions is not valid when + * ShapeTypeHasNoCorners(aElement) == true && aFlags == eIgnoreStrokeDashing, + * since when aElement has no corners the rendered linecap style depends on + * whether or not the stroke is dashed. + */ + static void GetStrokeOptions(AutoStrokeOptions* aStrokeOptions, + dom::SVGElement* aElement, + const ComputedStyle* aComputedStyle, + const SVGContextPaint* aContextPaint, + StrokeOptionFlags aFlags = eAllStrokeOptions); + + /** + * Returns the current computed value of the CSS property 'stroke-width' for + * the given element. aComputedStyle may be provided as an optimization. + * aContextPaint is also optional. + * + * Note that this function does NOT take account of the value of the 'stroke' + * and 'stroke-opacity' properties to, say, return zero if they are "none" or + * "0", respectively. + */ + static Float GetStrokeWidth(const dom::SVGElement* aElement, + const ComputedStyle* aComputedStyle, + const SVGContextPaint* aContextPaint); + + /* + * Get the number of CSS px (user units) per em (i.e. the em-height in user + * units) for an nsIContent + * + * XXX document the conditions under which these may fail, and what they + * return in those cases. + */ + static float GetFontSize(const mozilla::dom::Element* aElement); + static float GetFontSize(const nsIFrame* aFrame); + static float GetFontSize(const ComputedStyle*, nsPresContext*); + /* + * Get the number of CSS px (user units) per ex (i.e. the x-height in user + * units) for an nsIContent + * + * XXX document the conditions under which these may fail, and what they + * return in those cases. + */ + static float GetFontXHeight(const mozilla::dom::Element* aElement); + static float GetFontXHeight(const nsIFrame* aFrame); + static float GetFontXHeight(const ComputedStyle*, nsPresContext*); + + /* + * Report a localized error message to the error console. + */ + static nsresult ReportToConsole(const dom::Document* doc, + const char* aWarning, + const nsTArray& aParams); + + static Matrix GetCTM(dom::SVGElement* aElement, bool aScreenCTM); + + /** + * Gets the tight bounds-space stroke bounds of the non-scaling-stroked rect + * aRect. + * @param aToBoundsSpace transforms from source space to the space aBounds + * should be computed in. Must be rectilinear. + * @param aToNonScalingStrokeSpace transforms from source + * space to the space in which non-scaling stroke should be applied. + * Must be rectilinear. + */ + static void RectilinearGetStrokeBounds(const Rect& aRect, + const Matrix& aToBoundsSpace, + const Matrix& aToNonScalingStrokeSpace, + float aStrokeWidth, Rect* aBounds); + + /** + * Check if this is one of the SVG elements that SVG 1.1 Full says + * establishes a viewport: svg, symbol, image or foreignObject. + */ + static bool EstablishesViewport(const nsIContent* aContent); + + static mozilla::dom::SVGViewportElement* GetNearestViewportElement( + const nsIContent* aContent); + + /* enum for specifying coordinate direction for ObjectSpace/UserSpace */ + enum ctxDirection { X, Y, XY }; + + /** + * Computes sqrt((aWidth^2 + aHeight^2)/2); + */ + static double ComputeNormalizedHypotenuse(double aWidth, double aHeight); + + /* Returns the angle halfway between the two specified angles */ + static float AngleBisect(float a1, float a2); + + /* Generate a viewbox to viewport transformation matrix */ + + static Matrix GetViewBoxTransform( + float aViewportWidth, float aViewportHeight, float aViewboxX, + float aViewboxY, float aViewboxWidth, float aViewboxHeight, + const SVGAnimatedPreserveAspectRatio& aPreserveAspectRatio); + + static Matrix GetViewBoxTransform( + float aViewportWidth, float aViewportHeight, float aViewboxX, + float aViewboxY, float aViewboxWidth, float aViewboxHeight, + const SVGPreserveAspectRatio& aPreserveAspectRatio); + + static mozilla::RangedPtr GetStartRangedPtr( + const nsAString& aString); + + static mozilla::RangedPtr GetEndRangedPtr( + const nsAString& aString); + + /** + * Parses the sign (+ or -) of a number and moves aIter to the next + * character if a sign is found. + * @param aSignMultiplier [outparam] -1 if the sign is negative otherwise 1 + * @return false if we hit the end of the string (i.e. if aIter is initially + * at aEnd, or if we reach aEnd right after the sign character). + */ + static inline bool ParseOptionalSign( + mozilla::RangedPtr& aIter, + const mozilla::RangedPtr& aEnd, + int32_t& aSignMultiplier) { + if (aIter == aEnd) { + return false; + } + aSignMultiplier = *aIter == '-' ? -1 : 1; + + mozilla::RangedPtr iter(aIter); + + if (*iter == '-' || *iter == '+') { + ++iter; + if (iter == aEnd) { + return false; + } + } + aIter = iter; + return true; + } + + /** + * Parse a number of the form: + * number ::= integer ([Ee] integer)? | [+-]? [0-9]* "." [0-9]+ ([Ee] + * integer)? Parsing fails if the number cannot be represented by a floatType. + * If parsing succeeds, aIter is updated so that it points to the character + * after the end of the number, otherwise it is left unchanged + */ + template + static bool ParseNumber(mozilla::RangedPtr& aIter, + const mozilla::RangedPtr& aEnd, + floatType& aValue); + + /** + * Parse a number of the form: + * number ::= integer ([Ee] integer)? | [+-]? [0-9]* "." [0-9]+ ([Ee] + * integer)? Parsing fails if there is anything left over after the number, or + * the number cannot be represented by a floatType. + */ + template + static bool ParseNumber(const nsAString& aString, floatType& aValue); + + /** + * Parse an integer of the form: + * integer ::= [+-]? [0-9]+ + * The returned number is clamped to an int32_t if outside that range. + * If parsing succeeds, aIter is updated so that it points to the character + * after the end of the number, otherwise it is left unchanged + */ + static bool ParseInteger(mozilla::RangedPtr& aIter, + const mozilla::RangedPtr& aEnd, + int32_t& aValue); + + /** + * Parse an integer of the form: + * integer ::= [+-]? [0-9]+ + * The returned number is clamped to an int32_t if outside that range. + * Parsing fails if there is anything left over after the number. + */ + static bool ParseInteger(const nsAString& aString, int32_t& aValue); + + // XXX This should rather use LengthPercentage instead of + // StyleLengthPercentageUnion, but that's a type alias defined in + // ServoStyleConsts.h, and we don't want to avoid including that large header + // with all its dependencies. If a forwarding header were generated by + // cbindgen, we could include that. + // https://github.com/eqrion/cbindgen/issues/617 addresses this. + /** + * Converts a LengthPercentage into a userspace value, resolving percentage + * values relative to aContent's SVG viewport. + */ + static float CoordToFloat(const dom::SVGElement* aContent, + const StyleLengthPercentageUnion&, + uint8_t aCtxType = SVGContentUtils::XY); + /** + * Parse the SVG path string + * Returns a path + * string formatted as an SVG path + */ + static already_AddRefed GetPath( + const nsAString& aPathString); + + /** + * Returns true if aContent is one of the elements whose stroke is guaranteed + * to have no corners: circle or ellipse + */ + static bool ShapeTypeHasNoCorners(const nsIContent* aContent); + + /** + * Return one token in aString, aString may have leading and trailing + * whitespace; aSuccess will be set to false if there is no token or more than + * one token, otherwise it's set to true. + */ + static nsDependentSubstring GetAndEnsureOneToken(const nsAString& aString, + bool& aSuccess); +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGCONTENTUTILS_H_ diff --git a/dom/svg/SVGDataParser.cpp b/dom/svg/SVGDataParser.cpp new file mode 100644 index 0000000000..43112a2134 --- /dev/null +++ b/dom/svg/SVGDataParser.cpp @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGDataParser.h" +#include "nsContentUtils.h" +#include "SVGContentUtils.h" + +namespace mozilla { + +SVGDataParser::SVGDataParser(const nsAString& aValue) + : mIter(SVGContentUtils::GetStartRangedPtr(aValue)), + mEnd(SVGContentUtils::GetEndRangedPtr(aValue)) {} + +bool SVGDataParser::SkipCommaWsp() { + if (!SkipWsp()) { + // end of string + return false; + } + if (*mIter != ',') { + return true; + } + ++mIter; + return SkipWsp(); +} + +bool SVGDataParser::SkipWsp() { + while (mIter != mEnd) { + if (!nsContentUtils::IsHTMLWhitespace(*mIter)) { + return true; + } + ++mIter; + } + return false; +} + +} // namespace mozilla diff --git a/dom/svg/SVGDataParser.h b/dom/svg/SVGDataParser.h new file mode 100644 index 0000000000..9e0996361a --- /dev/null +++ b/dom/svg/SVGDataParser.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGDATAPARSER_H_ +#define DOM_SVG_SVGDATAPARSER_H_ + +#include +#include "mozilla/RangedPtr.h" +#include "nsStringFwd.h" + +namespace mozilla { + +//////////////////////////////////////////////////////////////////////// +// SVGDataParser: a simple base class for parsing values +// for path and transform values. +// +class SVGDataParser { + public: + explicit SVGDataParser(const nsAString& aValue); + + protected: + static bool IsAlpha(char16_t aCh) { + // Exclude non-ascii characters before calling isalpha + return (aCh & 0x7f) == aCh && isalpha(aCh); + } + + // Returns true if there are more characters to read, false otherwise. + bool SkipCommaWsp(); + + // Returns true if there are more characters to read, false otherwise. + bool SkipWsp(); + + mozilla::RangedPtr mIter; + const mozilla::RangedPtr mEnd; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGDATAPARSER_H_ diff --git a/dom/svg/SVGDefsElement.cpp b/dom/svg/SVGDefsElement.cpp new file mode 100644 index 0000000000..4fb68e1881 --- /dev/null +++ b/dom/svg/SVGDefsElement.cpp @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGDefsElement.h" +#include "mozilla/dom/SVGDefsElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Defs) + +namespace mozilla::dom { + +JSObject* SVGDefsElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGDefsElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGDefsElement::SVGDefsElement( + already_AddRefed&& aNodeInfo) + : SVGGraphicsElement(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGDefsElement) + +} // namespace mozilla::dom diff --git a/dom/svg/SVGDefsElement.h b/dom/svg/SVGDefsElement.h new file mode 100644 index 0000000000..9e77ddb6c5 --- /dev/null +++ b/dom/svg/SVGDefsElement.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGDEFSELEMENT_H_ +#define DOM_SVG_SVGDEFSELEMENT_H_ + +#include "SVGGraphicsElement.h" + +nsresult NS_NewSVGDefsElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +class SVGDefsElement final : public SVGGraphicsElement { + protected: + friend nsresult(::NS_NewSVGDefsElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGDefsElement(already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + // defs elements are not focusable. + bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) override { + return nsIContent::IsFocusableInternal(aTabIndex, aWithMouse); + } + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGDEFSELEMENT_H_ diff --git a/dom/svg/SVGDescElement.cpp b/dom/svg/SVGDescElement.cpp new file mode 100644 index 0000000000..95e8f0f168 --- /dev/null +++ b/dom/svg/SVGDescElement.cpp @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGDescElement.h" +#include "mozilla/dom/SVGDescElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Desc) + +namespace mozilla::dom { + +JSObject* SVGDescElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGDescElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGDescElement::SVGDescElement( + already_AddRefed&& aNodeInfo) + : SVGDescElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGDescElement) + +} // namespace mozilla::dom diff --git a/dom/svg/SVGDescElement.h b/dom/svg/SVGDescElement.h new file mode 100644 index 0000000000..0769db9519 --- /dev/null +++ b/dom/svg/SVGDescElement.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGDESCELEMENT_H_ +#define DOM_SVG_SVGDESCELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "SVGElement.h" + +nsresult NS_NewSVGDescElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGDescElementBase = SVGElement; + +class SVGDescElement final : public SVGDescElementBase { + protected: + friend nsresult(::NS_NewSVGDescElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGDescElement(already_AddRefed&& aNodeInfo); + + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGDESCELEMENT_H_ diff --git a/dom/svg/SVGDocument.cpp b/dom/svg/SVGDocument.cpp new file mode 100644 index 0000000000..6a69aaaa32 --- /dev/null +++ b/dom/svg/SVGDocument.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGDocument.h" + +#include "mozilla/css/Loader.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsLiteralString.h" +#include "mozilla/dom/Element.h" +#include "SVGElement.h" +#include "mozilla/StyleSheet.h" +#include "mozilla/StyleSheetInlines.h" + +using namespace mozilla::css; +using namespace mozilla::dom; + +namespace mozilla::dom { + +//---------------------------------------------------------------------- +// Implementation + +nsresult SVGDocument::Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const { + NS_ASSERTION(aNodeInfo->NodeInfoManager() == mNodeInfoManager, + "Can't import this document into another document!"); + + RefPtr clone = new SVGDocument(); + nsresult rv = CloneDocHelper(clone.get()); + NS_ENSURE_SUCCESS(rv, rv); + + clone.forget(aResult); + return NS_OK; +} + +} // namespace mozilla::dom + +//////////////////////////////////////////////////////////////////////// +// Exported creation functions + +nsresult NS_NewSVGDocument(Document** aInstancePtrResult) { + RefPtr doc = new SVGDocument(); + + nsresult rv = doc->Init(); + if (NS_FAILED(rv)) { + return rv; + } + + doc.forget(aInstancePtrResult); + return rv; +} diff --git a/dom/svg/SVGDocument.h b/dom/svg/SVGDocument.h new file mode 100644 index 0000000000..61ab9f74ac --- /dev/null +++ b/dom/svg/SVGDocument.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGDOCUMENT_H_ +#define DOM_SVG_SVGDOCUMENT_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/dom/XMLDocument.h" + +namespace mozilla { + +namespace dom { + +class SVGElement; +class SVGForeignObjectElement; + +class SVGDocument final : public XMLDocument { + public: + SVGDocument() : XMLDocument("image/svg+xml") { mType = eSVG; } + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; +}; + +inline SVGDocument* Document::AsSVGDocument() { + MOZ_ASSERT(IsSVGDocument()); + return static_cast(this); +} + +inline const SVGDocument* Document::AsSVGDocument() const { + MOZ_ASSERT(IsSVGDocument()); + return static_cast(this); +} + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGDOCUMENT_H_ diff --git a/dom/svg/SVGElement.cpp b/dom/svg/SVGElement.cpp new file mode 100644 index 0000000000..c6c8c461b6 --- /dev/null +++ b/dom/svg/SVGElement.cpp @@ -0,0 +1,2400 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGElement.h" + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/dom/MutationEventBinding.h" +#include "mozilla/dom/MutationObservers.h" +#include "mozilla/dom/CSSRuleBinding.h" +#include "mozilla/dom/SVGElementBinding.h" +#include "mozilla/dom/SVGGeometryElement.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/dom/SVGTests.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" +#include "mozilla/dom/Element.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/DeclarationBlock.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/InternalMutationEvent.h" +#include "mozilla/PresShell.h" +#include "mozilla/RestyleManager.h" +#include "mozilla/SMILAnimationController.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/Unused.h" + +#include "mozAutoDocUpdate.h" +#include "nsAttrValueOrString.h" +#include "nsCSSProps.h" +#include "nsCSSValue.h" +#include "nsContentUtils.h" +#include "nsDOMCSSAttrDeclaration.h" +#include "nsICSSDeclaration.h" +#include "nsIContentInlines.h" +#include "mozilla/dom/Document.h" +#include "nsError.h" +#include "nsGkAtoms.h" +#include "nsIFrame.h" +#include "nsQueryObject.h" +#include "nsLayoutUtils.h" +#include "SVGAnimatedNumberList.h" +#include "SVGAnimatedLengthList.h" +#include "SVGAnimatedPointList.h" +#include "SVGAnimatedPathSegList.h" +#include "SVGAnimatedTransformList.h" +#include "SVGAnimatedBoolean.h" +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedInteger.h" +#include "SVGAnimatedIntegerPair.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedNumber.h" +#include "SVGAnimatedNumberPair.h" +#include "SVGAnimatedOrient.h" +#include "SVGAnimatedString.h" +#include "SVGAnimatedViewBox.h" +#include "SVGGeometryProperty.h" +#include "SVGMotionSMILAttr.h" +#include + +// This is needed to ensure correct handling of calls to the +// vararg-list methods in this file: +// SVGElement::GetAnimated{Length,Number,Integer}Values +// See bug 547964 for details: +static_assert(sizeof(void*) == sizeof(nullptr), + "nullptr should be the correct size"); + +nsresult NS_NewSVGElement( + mozilla::dom::Element** aResult, + already_AddRefed&& aNodeInfo) { + RefPtr nodeInfo(aNodeInfo); + auto* nim = nodeInfo->NodeInfoManager(); + RefPtr it = + new (nim) mozilla::dom::SVGElement(nodeInfo.forget()); + nsresult rv = it->Init(); + + if (NS_FAILED(rv)) { + return rv; + } + + it.forget(aResult); + return rv; +} + +namespace mozilla::dom { +using namespace SVGUnitTypes_Binding; + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGElement) + +// Use the CC variant of this, even though this class does not define +// a new CC participant, to make QIing to the CC interfaces faster. +NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(SVGElement, SVGElementBase, + SVGElement) + +SVGEnumMapping SVGElement::sSVGUnitTypesMap[] = { + {nsGkAtoms::userSpaceOnUse, SVG_UNIT_TYPE_USERSPACEONUSE}, + {nsGkAtoms::objectBoundingBox, SVG_UNIT_TYPE_OBJECTBOUNDINGBOX}, + {nullptr, 0}}; + +SVGElement::SVGElement(already_AddRefed&& aNodeInfo) + : SVGElementBase(std::move(aNodeInfo)) {} + +SVGElement::~SVGElement() { + OwnerDoc()->UnscheduleSVGForPresAttrEvaluation(this); +} + +JSObject* SVGElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGElement_Binding::Wrap(aCx, this, aGivenProto); +} + +template +void SVGElement::AttributesInfo::ResetAll() { + for (uint32_t i = 0; i < mCount; ++i) { + Reset(i); + } +} + +template +void SVGElement::AttributesInfo::CopyAllFrom( + const AttributesInfo& aOther) { + MOZ_DIAGNOSTIC_ASSERT(mCount == aOther.mCount, + "Should only be called on clones"); + for (uint32_t i = 0; i < mCount; ++i) { + mValues[i] = aOther.mValues[i]; + } +} + +template <> +void SVGElement::LengthAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Init(mInfos[aAttrEnum].mCtxType, aAttrEnum, + mInfos[aAttrEnum].mDefaultValue, + mInfos[aAttrEnum].mDefaultUnitType); +} + +template <> +void SVGElement::LengthListAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].ClearBaseValue(aAttrEnum); + // caller notifies +} + +template <> +void SVGElement::NumberListAttributesInfo::Reset(uint8_t aAttrEnum) { + MOZ_ASSERT(aAttrEnum < mCount, "Bad attr enum"); + mValues[aAttrEnum].ClearBaseValue(aAttrEnum); + // caller notifies +} + +template <> +void SVGElement::NumberAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue); +} + +template <> +void SVGElement::NumberPairAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue1, + mInfos[aAttrEnum].mDefaultValue2); +} + +template <> +void SVGElement::IntegerAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue); +} + +template <> +void SVGElement::IntegerPairAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue1, + mInfos[aAttrEnum].mDefaultValue2); +} + +template <> +void SVGElement::BooleanAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue); +} + +template <> +void SVGElement::StringListAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Clear(); + // caller notifies +} + +template <> +void SVGElement::EnumAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Init(aAttrEnum, mInfos[aAttrEnum].mDefaultValue); +} + +template <> +void SVGElement::StringAttributesInfo::Reset(uint8_t aAttrEnum) { + mValues[aAttrEnum].Init(aAttrEnum); +} + +nsresult SVGElement::CopyInnerTo(mozilla::dom::Element* aDest) { + nsresult rv = Element::CopyInnerTo(aDest); + NS_ENSURE_SUCCESS(rv, rv); + + auto* dest = static_cast(aDest); + + // cloning a node must retain its internal nonce slot + if (auto* nonce = static_cast(GetProperty(nsGkAtoms::nonce))) { + dest->SetNonce(*nonce); + } + + // If our destination is a print document, copy all the relevant length values + // etc so that they match the state of the original node. + if (aDest->OwnerDoc()->IsStaticDocument()) { + dest->GetLengthInfo().CopyAllFrom(GetLengthInfo()); + dest->GetNumberInfo().CopyAllFrom(GetNumberInfo()); + dest->GetNumberPairInfo().CopyAllFrom(GetNumberPairInfo()); + dest->GetIntegerInfo().CopyAllFrom(GetIntegerInfo()); + dest->GetIntegerPairInfo().CopyAllFrom(GetIntegerPairInfo()); + dest->GetEnumInfo().CopyAllFrom(GetEnumInfo()); + dest->GetStringInfo().CopyAllFrom(GetStringInfo()); + dest->GetLengthListInfo().CopyAllFrom(GetLengthListInfo()); + dest->GetNumberListInfo().CopyAllFrom(GetNumberListInfo()); + } + + return NS_OK; +} + +//---------------------------------------------------------------------- +// SVGElement methods + +void SVGElement::DidAnimateClass() { + // For Servo, snapshot the element before we change it. + PresShell* presShell = OwnerDoc()->GetPresShell(); + if (presShell) { + if (nsPresContext* presContext = presShell->GetPresContext()) { + presContext->RestyleManager()->ClassAttributeWillBeChangedBySMIL(this); + } + } + + nsAutoString src; + mClassAttribute.GetAnimValue(src, this); + if (!mClassAnimAttr) { + mClassAnimAttr = MakeUnique(); + } + mClassAnimAttr->ParseAtomArray(src); + + // FIXME(emilio): This re-selector-matches, but we do the snapshot stuff right + // above... Is this needed anymore? + if (presShell) { + presShell->RestyleForAnimation(this, RestyleHint::RESTYLE_SELF); + } +} + +nsresult SVGElement::Init() { + // Set up length attributes - can't do this in the constructor + // because we can't do a virtual call at that point + + GetLengthInfo().ResetAll(); + GetNumberInfo().ResetAll(); + GetNumberPairInfo().ResetAll(); + GetIntegerInfo().ResetAll(); + GetIntegerPairInfo().ResetAll(); + GetBooleanInfo().ResetAll(); + GetEnumInfo().ResetAll(); + + if (SVGAnimatedOrient* orient = GetAnimatedOrient()) { + orient->Init(); + } + + if (SVGAnimatedViewBox* viewBox = GetAnimatedViewBox()) { + viewBox->Init(); + } + + if (SVGAnimatedPreserveAspectRatio* preserveAspectRatio = + GetAnimatedPreserveAspectRatio()) { + preserveAspectRatio->Init(); + } + + GetLengthListInfo().ResetAll(); + GetNumberListInfo().ResetAll(); + + // No need to reset SVGPointList since the default value is always the same + // (an empty list). + + // No need to reset SVGPathData since the default value is always the same + // (an empty list). + + GetStringInfo().ResetAll(); + return NS_OK; +} + +//---------------------------------------------------------------------- +// Implementation + +//---------------------------------------------------------------------- +// nsIContent methods + +nsresult SVGElement::BindToTree(BindContext& aContext, nsINode& aParent) { + nsresult rv = SVGElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + // Hide any nonce from the DOM, but keep the internal value of the + // nonce by copying and resetting the internal nonce value. + if (HasFlag(NODE_HAS_NONCE_AND_HEADER_CSP) && IsInComposedDoc() && + OwnerDoc()->GetBrowsingContext()) { + nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( + "SVGElement::ResetNonce::Runnable", + [self = RefPtr(this)]() { + nsAutoString nonce; + self->GetNonce(nonce); + self->SetAttr(kNameSpaceID_None, nsGkAtoms::nonce, u""_ns, true); + self->SetNonce(nonce); + })); + } + + return NS_OK; +} + +void SVGElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, bool aNotify) { + // We don't currently use nsMappedAttributes within SVG. If this changes, we + // need to be very careful because some nsAttrValues used by SVG point to + // member data of SVG elements and if an nsAttrValue outlives the SVG element + // whose data it points to (by virtue of being stored in + // mAttrs->mMappedAttributes, meaning it's shared between + // elements), the pointer will dangle. See bug 724680. + MOZ_ASSERT(!mAttrs.HasMappedAttrs(), + "Unexpected use of nsMappedAttributes within SVG"); + + // If this is an svg presentation attribute we need to map it into + // the content declaration block. + // XXX For some reason incremental mapping doesn't work, so for now + // just delete the style rule and lazily reconstruct it as needed). + if (aNamespaceID == kNameSpaceID_None && IsAttributeMapped(aName)) { + OwnerDoc()->ScheduleSVGForPresAttrEvaluation(this); + } + + if (IsEventAttributeName(aName) && aValue) { + MOZ_ASSERT(aValue->Type() == nsAttrValue::eString, + "Expected string value for script body"); + SetEventHandler(GetEventNameForAttr(aName), aValue->GetStringValue()); + } + + // The nonce will be copied over to an internal slot and cleared from the + // Element within BindToTree to avoid CSS Selector nonce exfiltration if + // the CSP list contains a header-delivered CSP. + if (nsGkAtoms::nonce == aName && kNameSpaceID_None == aNamespaceID) { + if (aValue) { + SetNonce(aValue->GetStringValue()); + if (OwnerDoc()->GetHasCSPDeliveredThroughHeader()) { + SetFlags(NODE_HAS_NONCE_AND_HEADER_CSP); + } + } else { + RemoveNonce(); + } + } + + return SVGElementBase::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue, + aSubjectPrincipal, aNotify); +} + +bool SVGElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) { + nsresult rv = NS_OK; + bool foundMatch = false; + bool didSetResult = false; + + if (aNamespaceID == kNameSpaceID_None) { + // Check for SVGAnimatedLength attribute + LengthAttributesInfo lengthInfo = GetLengthInfo(); + + uint32_t i; + for (i = 0; i < lengthInfo.mCount; i++) { + if (aAttribute == lengthInfo.mInfos[i].mName) { + rv = lengthInfo.mValues[i].SetBaseValueString(aValue, this, false); + if (NS_FAILED(rv)) { + lengthInfo.Reset(i); + } else { + aResult.SetTo(lengthInfo.mValues[i], &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + + if (!foundMatch) { + // Check for SVGAnimatedLengthList attribute + LengthListAttributesInfo lengthListInfo = GetLengthListInfo(); + for (i = 0; i < lengthListInfo.mCount; i++) { + if (aAttribute == lengthListInfo.mInfos[i].mName) { + rv = lengthListInfo.mValues[i].SetBaseValueString(aValue); + if (NS_FAILED(rv)) { + lengthListInfo.Reset(i); + } else { + aResult.SetTo(lengthListInfo.mValues[i].GetBaseValue(), &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedNumberList attribute + NumberListAttributesInfo numberListInfo = GetNumberListInfo(); + for (i = 0; i < numberListInfo.mCount; i++) { + if (aAttribute == numberListInfo.mInfos[i].mName) { + rv = numberListInfo.mValues[i].SetBaseValueString(aValue); + if (NS_FAILED(rv)) { + numberListInfo.Reset(i); + } else { + aResult.SetTo(numberListInfo.mValues[i].GetBaseValue(), &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedPointList attribute + if (GetPointListAttrName() == aAttribute) { + if (SVGAnimatedPointList* pointList = GetAnimatedPointList()) { + pointList->SetBaseValueString(aValue); + // The spec says we parse everything up to the failure, so we DON'T + // need to check the result of SetBaseValueString or call + // pointList->ClearBaseValue() if it fails + aResult.SetTo(pointList->GetBaseValue(), &aValue); + didSetResult = true; + foundMatch = true; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedPathSegList attribute + if (GetPathDataAttrName() == aAttribute) { + if (SVGAnimatedPathSegList* segList = GetAnimPathSegList()) { + segList->SetBaseValueString(aValue); + // The spec says we parse everything up to the failure, so we DON'T + // need to check the result of SetBaseValueString or call + // segList->ClearBaseValue() if it fails + aResult.SetTo(segList->GetBaseValue(), &aValue); + didSetResult = true; + foundMatch = true; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedNumber attribute + NumberAttributesInfo numberInfo = GetNumberInfo(); + for (i = 0; i < numberInfo.mCount; i++) { + if (aAttribute == numberInfo.mInfos[i].mName) { + rv = numberInfo.mValues[i].SetBaseValueString(aValue, this); + if (NS_FAILED(rv)) { + numberInfo.Reset(i); + } else { + aResult.SetTo(numberInfo.mValues[i].GetBaseValue(), &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedNumberPair attribute + NumberPairAttributesInfo numberPairInfo = GetNumberPairInfo(); + for (i = 0; i < numberPairInfo.mCount; i++) { + if (aAttribute == numberPairInfo.mInfos[i].mName) { + rv = numberPairInfo.mValues[i].SetBaseValueString(aValue, this); + if (NS_FAILED(rv)) { + numberPairInfo.Reset(i); + } else { + aResult.SetTo(numberPairInfo.mValues[i], &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedInteger attribute + IntegerAttributesInfo integerInfo = GetIntegerInfo(); + for (i = 0; i < integerInfo.mCount; i++) { + if (aAttribute == integerInfo.mInfos[i].mName) { + rv = integerInfo.mValues[i].SetBaseValueString(aValue, this); + if (NS_FAILED(rv)) { + integerInfo.Reset(i); + } else { + aResult.SetTo(integerInfo.mValues[i].GetBaseValue(), &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedIntegerPair attribute + IntegerPairAttributesInfo integerPairInfo = GetIntegerPairInfo(); + for (i = 0; i < integerPairInfo.mCount; i++) { + if (aAttribute == integerPairInfo.mInfos[i].mName) { + rv = integerPairInfo.mValues[i].SetBaseValueString(aValue, this); + if (NS_FAILED(rv)) { + integerPairInfo.Reset(i); + } else { + aResult.SetTo(integerPairInfo.mValues[i], &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedBoolean attribute + BooleanAttributesInfo booleanInfo = GetBooleanInfo(); + for (i = 0; i < booleanInfo.mCount; i++) { + if (aAttribute == booleanInfo.mInfos[i].mName) { + nsAtom* valAtom = NS_GetStaticAtom(aValue); + rv = valAtom ? booleanInfo.mValues[i].SetBaseValueAtom(valAtom, this) + : NS_ERROR_DOM_SYNTAX_ERR; + if (NS_FAILED(rv)) { + booleanInfo.Reset(i); + } else { + aResult.SetTo(valAtom); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for SVGAnimatedEnumeration attribute + EnumAttributesInfo enumInfo = GetEnumInfo(); + for (i = 0; i < enumInfo.mCount; i++) { + if (aAttribute == enumInfo.mInfos[i].mName) { + RefPtr valAtom = NS_Atomize(aValue); + if (!enumInfo.mValues[i].SetBaseValueAtom(valAtom, this)) { + // Exact error value does not matter; we just need to mark the + // parse as failed. + rv = NS_ERROR_FAILURE; + enumInfo.Reset(i); + } else { + aResult.SetTo(valAtom); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for conditional processing attributes + nsCOMPtr tests = do_QueryObject(this); + if (tests && tests->ParseConditionalProcessingAttribute( + aAttribute, aValue, aResult)) { + foundMatch = true; + } + } + + if (!foundMatch) { + // Check for StringList attribute + StringListAttributesInfo stringListInfo = GetStringListInfo(); + for (i = 0; i < stringListInfo.mCount; i++) { + if (aAttribute == stringListInfo.mInfos[i].mName) { + rv = stringListInfo.mValues[i].SetValue(aValue); + if (NS_FAILED(rv)) { + stringListInfo.Reset(i); + } else { + aResult.SetTo(stringListInfo.mValues[i], &aValue); + didSetResult = true; + } + foundMatch = true; + break; + } + } + } + + if (!foundMatch) { + // Check for orient attribute + if (aAttribute == nsGkAtoms::orient) { + SVGAnimatedOrient* orient = GetAnimatedOrient(); + if (orient) { + rv = orient->SetBaseValueString(aValue, this, false); + if (NS_FAILED(rv)) { + orient->Init(); + } else { + aResult.SetTo(*orient, &aValue); + didSetResult = true; + } + foundMatch = true; + } + // Check for viewBox attribute + } else if (aAttribute == nsGkAtoms::viewBox) { + SVGAnimatedViewBox* viewBox = GetAnimatedViewBox(); + if (viewBox) { + rv = viewBox->SetBaseValueString(aValue, this, false); + if (NS_FAILED(rv)) { + viewBox->Init(); + } else { + aResult.SetTo(*viewBox, &aValue); + didSetResult = true; + } + foundMatch = true; + } + // Check for preserveAspectRatio attribute + } else if (aAttribute == nsGkAtoms::preserveAspectRatio) { + SVGAnimatedPreserveAspectRatio* preserveAspectRatio = + GetAnimatedPreserveAspectRatio(); + if (preserveAspectRatio) { + rv = preserveAspectRatio->SetBaseValueString(aValue, this, false); + if (NS_FAILED(rv)) { + preserveAspectRatio->Init(); + } else { + aResult.SetTo(*preserveAspectRatio, &aValue); + didSetResult = true; + } + foundMatch = true; + } + // Check for SVGAnimatedTransformList attribute + } else if (GetTransformListAttrName() == aAttribute) { + // The transform attribute is being set, so we must ensure that the + // SVGAnimatedTransformList is/has been allocated: + SVGAnimatedTransformList* transformList = + GetAnimatedTransformList(DO_ALLOCATE); + rv = transformList->SetBaseValueString(aValue, this); + if (NS_FAILED(rv)) { + transformList->ClearBaseValue(); + } else { + aResult.SetTo(transformList->GetBaseValue(), &aValue); + didSetResult = true; + } + foundMatch = true; + } else if (aAttribute == nsGkAtoms::tabindex) { + didSetResult = aResult.ParseIntValue(aValue); + foundMatch = true; + } + } + + if (aAttribute == nsGkAtoms::_class) { + mClassAttribute.SetBaseValue(aValue, this, false); + aResult.ParseAtomArray(aValue); + return true; + } + + if (aAttribute == nsGkAtoms::rel) { + aResult.ParseAtomArray(aValue); + return true; + } + } + + if (!foundMatch) { + // Check for SVGAnimatedString attribute + StringAttributesInfo stringInfo = GetStringInfo(); + for (uint32_t i = 0; i < stringInfo.mCount; i++) { + if (aNamespaceID == stringInfo.mInfos[i].mNamespaceID && + aAttribute == stringInfo.mInfos[i].mName) { + stringInfo.mValues[i].SetBaseValue(aValue, this, false); + foundMatch = true; + break; + } + } + } + + if (foundMatch) { + if (NS_FAILED(rv)) { + ReportAttributeParseFailure(OwnerDoc(), aAttribute, aValue); + return false; + } + if (!didSetResult) { + aResult.SetTo(aValue); + } + return true; + } + + return SVGElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue, + aMaybeScriptedPrincipal, aResult); +} + +void SVGElement::UnsetAttrInternal(int32_t aNamespaceID, nsAtom* aName, + bool aNotify) { + // XXXbz there's a bunch of redundancy here with AfterSetAttr. + // Maybe consolidate? + + if (aNamespaceID == kNameSpaceID_None) { + if (IsEventAttributeName(aName)) { + EventListenerManager* manager = GetExistingListenerManager(); + if (manager) { + nsAtom* eventName = GetEventNameForAttr(aName); + manager->RemoveEventHandler(eventName); + } + return; + } + + // Check if this is a length attribute going away + LengthAttributesInfo lenInfo = GetLengthInfo(); + + for (uint32_t i = 0; i < lenInfo.mCount; i++) { + if (aName == lenInfo.mInfos[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + lenInfo.Reset(i); + return; + } + } + + // Check if this is a length list attribute going away + LengthListAttributesInfo lengthListInfo = GetLengthListInfo(); + + for (uint32_t i = 0; i < lengthListInfo.mCount; i++) { + if (aName == lengthListInfo.mInfos[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + lengthListInfo.Reset(i); + return; + } + } + + // Check if this is a number list attribute going away + NumberListAttributesInfo numberListInfo = GetNumberListInfo(); + + for (uint32_t i = 0; i < numberListInfo.mCount; i++) { + if (aName == numberListInfo.mInfos[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + numberListInfo.Reset(i); + return; + } + } + + // Check if this is a point list attribute going away + if (GetPointListAttrName() == aName) { + SVGAnimatedPointList* pointList = GetAnimatedPointList(); + if (pointList) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + pointList->ClearBaseValue(); + return; + } + } + + // Check if this is a path segment list attribute going away + if (GetPathDataAttrName() == aName) { + SVGAnimatedPathSegList* segList = GetAnimPathSegList(); + if (segList) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + segList->ClearBaseValue(); + return; + } + } + + // Check if this is a number attribute going away + NumberAttributesInfo numInfo = GetNumberInfo(); + + for (uint32_t i = 0; i < numInfo.mCount; i++) { + if (aName == numInfo.mInfos[i].mName) { + numInfo.Reset(i); + return; + } + } + + // Check if this is a number pair attribute going away + NumberPairAttributesInfo numPairInfo = GetNumberPairInfo(); + + for (uint32_t i = 0; i < numPairInfo.mCount; i++) { + if (aName == numPairInfo.mInfos[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + numPairInfo.Reset(i); + return; + } + } + + // Check if this is an integer attribute going away + IntegerAttributesInfo intInfo = GetIntegerInfo(); + + for (uint32_t i = 0; i < intInfo.mCount; i++) { + if (aName == intInfo.mInfos[i].mName) { + intInfo.Reset(i); + return; + } + } + + // Check if this is an integer pair attribute going away + IntegerPairAttributesInfo intPairInfo = GetIntegerPairInfo(); + + for (uint32_t i = 0; i < intPairInfo.mCount; i++) { + if (aName == intPairInfo.mInfos[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + intPairInfo.Reset(i); + return; + } + } + + // Check if this is a boolean attribute going away + BooleanAttributesInfo boolInfo = GetBooleanInfo(); + + for (uint32_t i = 0; i < boolInfo.mCount; i++) { + if (aName == boolInfo.mInfos[i].mName) { + boolInfo.Reset(i); + return; + } + } + + // Check if this is an enum attribute going away + EnumAttributesInfo enumInfo = GetEnumInfo(); + + for (uint32_t i = 0; i < enumInfo.mCount; i++) { + if (aName == enumInfo.mInfos[i].mName) { + enumInfo.Reset(i); + return; + } + } + + // Check if this is an orient attribute going away + if (aName == nsGkAtoms::orient) { + SVGAnimatedOrient* orient = GetAnimatedOrient(); + if (orient) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + orient->Init(); + return; + } + } + + // Check if this is a viewBox attribute going away + if (aName == nsGkAtoms::viewBox) { + SVGAnimatedViewBox* viewBox = GetAnimatedViewBox(); + if (viewBox) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + viewBox->Init(); + return; + } + } + + // Check if this is a preserveAspectRatio attribute going away + if (aName == nsGkAtoms::preserveAspectRatio) { + SVGAnimatedPreserveAspectRatio* preserveAspectRatio = + GetAnimatedPreserveAspectRatio(); + if (preserveAspectRatio) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + preserveAspectRatio->Init(); + return; + } + } + + // Check if this is a transform list attribute going away + if (GetTransformListAttrName() == aName) { + SVGAnimatedTransformList* transformList = GetAnimatedTransformList(); + if (transformList) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + transformList->ClearBaseValue(); + return; + } + } + + // Check for conditional processing attributes + nsCOMPtr tests = do_QueryObject(this); + if (tests && tests->IsConditionalProcessingAttribute(aName)) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + tests->UnsetAttr(aName); + return; + } + + // Check if this is a string list attribute going away + StringListAttributesInfo stringListInfo = GetStringListInfo(); + + for (uint32_t i = 0; i < stringListInfo.mCount; i++) { + if (aName == stringListInfo.mInfos[i].mName) { + MaybeSerializeAttrBeforeRemoval(aName, aNotify); + stringListInfo.Reset(i); + return; + } + } + + if (aName == nsGkAtoms::_class) { + mClassAttribute.Init(); + return; + } + } + + // Check if this is a string attribute going away + StringAttributesInfo stringInfo = GetStringInfo(); + + for (uint32_t i = 0; i < stringInfo.mCount; i++) { + if (aNamespaceID == stringInfo.mInfos[i].mNamespaceID && + aName == stringInfo.mInfos[i].mName) { + stringInfo.Reset(i); + return; + } + } +} + +void SVGElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, bool aNotify) { + if (!aValue) { + UnsetAttrInternal(aNamespaceID, aName, aNotify); + } + return SVGElementBase::BeforeSetAttr(aNamespaceID, aName, aValue, aNotify); +} + +nsChangeHint SVGElement::GetAttributeChangeHint(const nsAtom* aAttribute, + int32_t aModType) const { + nsChangeHint retval = + SVGElementBase::GetAttributeChangeHint(aAttribute, aModType); + + nsCOMPtr tests = do_QueryObject(const_cast(this)); + if (tests && tests->IsConditionalProcessingAttribute(aAttribute)) { + // It would be nice to only reconstruct the frame if the value returned by + // SVGTests::PassesConditionalProcessingTests has changed, but we don't + // know that + retval |= nsChangeHint_ReconstructFrame; + } + return retval; +} + +void SVGElement::NodeInfoChanged(Document* aOldDoc) { + SVGElementBase::NodeInfoChanged(aOldDoc); + aOldDoc->UnscheduleSVGForPresAttrEvaluation(this); + mContentDeclarationBlock = nullptr; + OwnerDoc()->ScheduleSVGForPresAttrEvaluation(this); +} + +NS_IMETHODIMP_(bool) +SVGElement::IsAttributeMapped(const nsAtom* name) const { + if (name == nsGkAtoms::lang) { + return true; + } + + if (IsSVGAnimationElement()) { + return SVGElementBase::IsAttributeMapped(name); + } + + static const MappedAttributeEntry attributes[] = { + // Properties that we don't support are commented out. + // { nsGkAtoms::alignment_baseline }, + // { nsGkAtoms::baseline_shift }, + {nsGkAtoms::clip}, + {nsGkAtoms::clip_path}, + {nsGkAtoms::clip_rule}, + {nsGkAtoms::color}, + {nsGkAtoms::colorInterpolation}, + {nsGkAtoms::colorInterpolationFilters}, + {nsGkAtoms::cursor}, + {nsGkAtoms::direction}, + {nsGkAtoms::display}, + {nsGkAtoms::dominant_baseline}, + {nsGkAtoms::fill}, + {nsGkAtoms::fill_opacity}, + {nsGkAtoms::fill_rule}, + {nsGkAtoms::filter}, + {nsGkAtoms::flood_color}, + {nsGkAtoms::flood_opacity}, + {nsGkAtoms::font_family}, + {nsGkAtoms::font_size}, + {nsGkAtoms::font_size_adjust}, + {nsGkAtoms::font_stretch}, + {nsGkAtoms::font_style}, + {nsGkAtoms::font_variant}, + {nsGkAtoms::fontWeight}, + {nsGkAtoms::image_rendering}, + {nsGkAtoms::letter_spacing}, + {nsGkAtoms::lighting_color}, + {nsGkAtoms::marker_end}, + {nsGkAtoms::marker_mid}, + {nsGkAtoms::marker_start}, + {nsGkAtoms::mask}, + {nsGkAtoms::mask_type}, + {nsGkAtoms::opacity}, + {nsGkAtoms::overflow}, + {nsGkAtoms::paint_order}, + {nsGkAtoms::pointer_events}, + {nsGkAtoms::shape_rendering}, + {nsGkAtoms::stop_color}, + {nsGkAtoms::stop_opacity}, + {nsGkAtoms::stroke}, + {nsGkAtoms::stroke_dasharray}, + {nsGkAtoms::stroke_dashoffset}, + {nsGkAtoms::stroke_linecap}, + {nsGkAtoms::stroke_linejoin}, + {nsGkAtoms::stroke_miterlimit}, + {nsGkAtoms::stroke_opacity}, + {nsGkAtoms::stroke_width}, + {nsGkAtoms::text_anchor}, + {nsGkAtoms::text_decoration}, + {nsGkAtoms::text_rendering}, + {nsGkAtoms::transform_origin}, + {nsGkAtoms::unicode_bidi}, + {nsGkAtoms::vector_effect}, + {nsGkAtoms::visibility}, + {nsGkAtoms::white_space}, + {nsGkAtoms::word_spacing}, + {nsGkAtoms::writing_mode}, + {nullptr}}; + + static const MappedAttributeEntry* const map[] = {attributes}; + + return FindAttributeDependence(name, map) || + SVGElementBase::IsAttributeMapped(name); +} + +//---------------------------------------------------------------------- +// Element methods + +// forwarded to Element implementations + +//---------------------------------------------------------------------- + +SVGSVGElement* SVGElement::GetOwnerSVGElement() { + nsIContent* ancestor = GetFlattenedTreeParent(); + + while (ancestor && ancestor->IsSVGElement()) { + if (ancestor->IsSVGElement(nsGkAtoms::foreignObject)) { + return nullptr; + } + if (auto* svg = SVGSVGElement::FromNode(ancestor)) { + return svg; + } + ancestor = ancestor->GetFlattenedTreeParent(); + } + + // we don't have an ancestor element... + return nullptr; +} + +SVGElement* SVGElement::GetViewportElement() { + return SVGContentUtils::GetNearestViewportElement(this); +} + +already_AddRefed SVGElement::ClassName() { + return mClassAttribute.ToDOMAnimatedString(this); +} + +/* static */ +bool SVGElement::UpdateDeclarationBlockFromLength( + DeclarationBlock& aBlock, nsCSSPropertyID aPropId, + const SVGAnimatedLength& aLength, ValToUse aValToUse) { + aBlock.AssertMutable(); + + float value; + if (aValToUse == ValToUse::Anim) { + value = aLength.GetAnimValInSpecifiedUnits(); + } else { + MOZ_ASSERT(aValToUse == ValToUse::Base); + value = aLength.GetBaseValInSpecifiedUnits(); + } + + // SVG parser doesn't check non-negativity of some parsed value, + // we should not pass those to CSS side. + if (value < 0 && + SVGGeometryProperty::IsNonNegativeGeometryProperty(aPropId)) { + return false; + } + + nsCSSUnit cssUnit = SVGGeometryProperty::SpecifiedUnitTypeToCSSUnit( + aLength.GetSpecifiedUnitType()); + + if (cssUnit == eCSSUnit_Percent) { + Servo_DeclarationBlock_SetPercentValue(aBlock.Raw(), aPropId, + value / 100.f); + } else { + Servo_DeclarationBlock_SetLengthValue(aBlock.Raw(), aPropId, value, + cssUnit); + } + + return true; +} + +/* static */ +bool SVGElement::UpdateDeclarationBlockFromPath( + DeclarationBlock& aBlock, const SVGAnimatedPathSegList& aPath, + ValToUse aValToUse) { + aBlock.AssertMutable(); + + const SVGPathData& pathData = + aValToUse == ValToUse::Anim ? aPath.GetAnimValue() : aPath.GetBaseValue(); + + // SVGPathData::mData is fallible but rust binding accepts nsTArray only, so + // we need to point to one or the other. Fortunately, fallible and infallible + // array types can be implicitly converted provided they are const. + // + // FIXME: here we just convert the data structure from cpp verion into rust + // version. We don't do any normalization for the path data from d attribute. + // Based on the current discussion of https://github.com/w3c/svgwg/issues/321, + // we may have to convert the relative commands into absolute commands. + // The normalization should be fixed in Bug 1489392. Besides, Bug 1714238 + // will use the same data structure, so we may simplify this more. + const nsTArray& asInFallibleArray = pathData.RawData(); + Servo_DeclarationBlock_SetPathValue(aBlock.Raw(), eCSSProperty_d, + &asInFallibleArray); + return true; +} + +//------------------------------------------------------------------------ +// Helper class: MappedAttrParser, for parsing values of mapped attributes + +namespace { + +class MOZ_STACK_CLASS MappedAttrParser { + public: + explicit MappedAttrParser(SVGElement& aElement, + already_AddRefed aDecl) + : mElement(aElement), mDecl(aDecl) { + if (mDecl) { + mDecl->AssertMutable(); + Servo_DeclarationBlock_Clear(mDecl->Raw()); + } + } + ~MappedAttrParser() { + MOZ_ASSERT(!mDecl, + "If mDecl was initialized, it should have been returned via " + "TakeDeclarationBlock (and have its pointer cleared)"); + }; + + // Parses a mapped attribute value. + void ParseMappedAttrValue(nsAtom* aMappedAttrName, + const nsAString& aMappedAttrValue); + + void TellStyleAlreadyParsedResult(nsAtom const* aAtom, + SVGAnimatedLength const& aLength); + void TellStyleAlreadyParsedResult(const SVGAnimatedPathSegList& aPath); + + // If we've parsed any values for mapped attributes, this method returns the + // already_AddRefed css::Declaration that incorporates the parsed + // values. Otherwise, this method returns null. + already_AddRefed TakeDeclarationBlock() { + return mDecl.forget(); + } + + DeclarationBlock& EnsureDeclarationBlock() { + if (!mDecl) { + mDecl = new DeclarationBlock(); + } + return *mDecl; + } + + URLExtraData& EnsureExtraData() { + if (!mExtraData) { + mExtraData = mElement.GetURLDataForStyleAttr(); + } + return *mExtraData; + } + + private: + // For reporting use counters + SVGElement& mElement; + + // Declaration for storing parsed values (lazily initialized). + RefPtr mDecl; + + // URL data for parsing stuff. Also lazy. + RefPtr mExtraData; +}; + +void MappedAttrParser::ParseMappedAttrValue(nsAtom* aMappedAttrName, + const nsAString& aMappedAttrValue) { + // Get the nsCSSPropertyID ID for our mapped attribute. + nsCSSPropertyID propertyID = + nsCSSProps::LookupProperty(nsAutoAtomCString(aMappedAttrName)); + if (propertyID != eCSSProperty_UNKNOWN) { + bool changed = false; // outparam for ParseProperty. + NS_ConvertUTF16toUTF8 value(aMappedAttrValue); + + auto* doc = mElement.OwnerDoc(); + changed = Servo_DeclarationBlock_SetPropertyById( + EnsureDeclarationBlock().Raw(), propertyID, &value, false, + &EnsureExtraData(), ParsingMode::AllowUnitlessLength, + doc->GetCompatibilityMode(), doc->CSSLoader(), StyleCssRuleType::Style, + {}); + + // TODO(emilio): If we want to record these from CSSOM more generally, we + // can pass the document use counters down the FFI call. For now manually + // count them. + if (changed && StaticPrefs::layout_css_use_counters_enabled()) { + UseCounter useCounter = nsCSSProps::UseCounterFor(propertyID); + MOZ_ASSERT(useCounter != eUseCounter_UNKNOWN); + doc->SetUseCounter(useCounter); + } + return; + } + MOZ_ASSERT(aMappedAttrName == nsGkAtoms::lang, + "Only 'lang' should be unrecognized!"); + // CSS parser doesn't know about 'lang', so we need to handle it specially. + if (aMappedAttrName == nsGkAtoms::lang) { + propertyID = eCSSProperty__x_lang; + RefPtr atom = NS_Atomize(aMappedAttrValue); + Servo_DeclarationBlock_SetIdentStringValue(EnsureDeclarationBlock().Raw(), + propertyID, atom); + } +} + +void MappedAttrParser::TellStyleAlreadyParsedResult( + nsAtom const* aAtom, SVGAnimatedLength const& aLength) { + nsCSSPropertyID propertyID = + nsCSSProps::LookupProperty(nsAutoAtomCString(aAtom)); + SVGElement::UpdateDeclarationBlockFromLength(EnsureDeclarationBlock(), + propertyID, aLength, + SVGElement::ValToUse::Base); +} + +void MappedAttrParser::TellStyleAlreadyParsedResult( + const SVGAnimatedPathSegList& aPath) { + SVGElement::UpdateDeclarationBlockFromPath(EnsureDeclarationBlock(), aPath, + SVGElement::ValToUse::Base); +} + +} // namespace + +//---------------------------------------------------------------------- +// Implementation Helpers: + +void SVGElement::UpdateContentDeclarationBlock() { + MappedAttrParser mappedAttrParser(*this, mContentDeclarationBlock.forget()); + + bool lengthAffectsStyle = + SVGGeometryProperty::ElementMapsLengthsToStyle(this); + + uint32_t i = 0; + while (BorrowedAttrInfo info = GetAttrInfoAt(i++)) { + const nsAttrName* attrName = info.mName; + if (!attrName->IsAtom() || !IsAttributeMapped(attrName->Atom())) { + continue; + } + + if (attrName->Atom() == nsGkAtoms::lang && + HasAttr(kNameSpaceID_XML, nsGkAtoms::lang)) { + // xml:lang has precedence, and will get set via Gecko_GetXMLLangValue(). + continue; + } + + if (lengthAffectsStyle) { + auto const* length = GetAnimatedLength(attrName->Atom()); + + if (length && length->HasBaseVal()) { + // This is an element with geometry property set via SVG attribute, + // and the attribute is already successfully parsed. We want to go + // through the optimized path to tell the style system the result + // directly, rather than let it parse the same thing again. + mappedAttrParser.TellStyleAlreadyParsedResult(attrName->Atom(), + *length); + continue; + } + } + + if (attrName->Equals(nsGkAtoms::d, kNameSpaceID_None)) { + const auto* path = GetAnimPathSegList(); + // Note: Only SVGPathElement has d attribute. + MOZ_ASSERT( + path, + "SVGPathElement should have the non-null SVGAnimatedPathSegList"); + // The attribute should have been already successfully parsed. + // We want to go through the optimized path to tell the style system + // the result directly, rather than let it parse the same thing again. + mappedAttrParser.TellStyleAlreadyParsedResult(*path); + // Some other notes: + // The syntax of CSS d property is different from SVG d attribute. + // 1. CSS d proeprty accepts: none | path(); + // 2. SVG d attribtue accepts: none | + // So we cannot use css parser to parse the SVG d attribute directly. + // Besides, |mAttrs.AttrAt(i)| removes the quotes already, so the svg path + // in |mAttrs.AttrAt(i)| would be something like `M0,0L1,1z` without the + // quotes. So css tokenizer cannot recognize this as a quoted string, and + // so svg_path::SVGPathData::parse() doesn't work for this. Fortunately, + // we still can rely on the parsed result from + // SVGElement::ParseAttribute() for d attribute. + continue; + } + + nsAutoString value; + info.mValue->ToString(value); + mappedAttrParser.ParseMappedAttrValue(attrName->Atom(), value); + } + mContentDeclarationBlock = mappedAttrParser.TakeDeclarationBlock(); +} + +const DeclarationBlock* SVGElement::GetContentDeclarationBlock() const { + return mContentDeclarationBlock; +} + +/** + * Helper methods for the type-specific WillChangeXXX methods. + * + * This method sends out appropriate pre-change notifications so that selector + * restyles (e.g. due to changes that cause |elem[attr="val"]| to start/stop + * matching) work, and it returns an nsAttrValue that _may_ contain the + * attribute's pre-change value. + * + * The nsAttrValue returned by this method depends on whether there are + * mutation event listeners listening for changes to this element's attributes. + * If not, then the object returned is empty. If there are, then the + * nsAttrValue returned contains a serialized copy of the attribute's value + * prior to the change, and this object should be passed to the corresponding + * DidChangeXXX method call (assuming a WillChangeXXX call is required for the + * SVG type - see comment below). This is necessary so that the 'prevValue' + * property of the mutation event that is dispatched will correctly contain the + * old value. + * + * The reason we need to serialize the old value if there are mutation + * event listeners is because the underlying nsAttrValue for the attribute + * points directly to a parsed representation of the attribute (e.g. an + * SVGAnimatedLengthList*) that is a member of the SVG element. That object + * will have changed by the time DidChangeXXX has been called, so without the + * serialization of the old attribute value that we provide, DidChangeXXX + * would have no way to get the old value to pass to SetAttrAndNotify. + * + * We only return the old value when there are mutation event listeners because + * it's not needed otherwise, and because it's expensive to serialize the old + * value. This is especially true for list type attributes, which may be built + * up via the SVG DOM resulting in a large number of Will/DidModifyXXX calls + * before the script finally finishes setting the attribute. + * + * Note that unlike using SetParsedAttr, using Will/DidChangeXXX does NOT check + * and filter out redundant changes. Before calling WillChangeXXX, the caller + * should check whether the new and old values are actually the same, and skip + * calling Will/DidChangeXXX if they are. + * + * Also note that not all SVG types use this scheme. For types that can be + * represented by an nsAttrValue without pointing back to an SVG object (e.g. + * enums, booleans, integers) we can simply use SetParsedAttr which will do all + * of the above for us. For such types there is no matching WillChangeXXX + * method, only DidChangeXXX which calls SetParsedAttr. + */ +nsAttrValue SVGElement::WillChangeValue( + nsAtom* aName, const mozAutoDocUpdate& aProofOfUpdate) { + // We need an empty attr value: + // a) to pass to BeforeSetAttr when GetParsedAttr returns nullptr + // b) to store the old value in the case we have mutation listeners + // + // We can use the same value for both purposes, because if GetParsedAttr + // returns non-null its return value is what will get passed to BeforeSetAttr, + // not matter what our mutation listener situation is. + // + // Also, we should be careful to always return this value to benefit from + // return value optimization. + nsAttrValue emptyOrOldAttrValue; + const nsAttrValue* attrValue = GetParsedAttr(aName); + + // We only need to set the old value if we have listeners since otherwise it + // isn't used. + if (attrValue && nsContentUtils::HasMutationListeners( + this, NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this)) { + emptyOrOldAttrValue.SetToSerialized(*attrValue); + } + + uint8_t modType = + attrValue ? static_cast(MutationEvent_Binding::MODIFICATION) + : static_cast(MutationEvent_Binding::ADDITION); + MutationObservers::NotifyAttributeWillChange(this, kNameSpaceID_None, aName, + modType); + + // This is not strictly correct--the attribute value parameter for + // BeforeSetAttr should reflect the value that *will* be set but that implies + // allocating, e.g. an extra SVGAnimatedLength, and isn't necessary at the + // moment since no SVG elements overload BeforeSetAttr. For now we just pass + // the current value. + const nsAttrValue* value = attrValue ? attrValue : &emptyOrOldAttrValue; + BeforeSetAttr(kNameSpaceID_None, aName, value, kNotifyDocumentObservers); + return emptyOrOldAttrValue; +} + +/** + * Helper methods for the type-specific DidChangeXXX methods. + * + * aEmptyOrOldValue will normally be the object returned from the corresponding + * WillChangeXXX call. This is because: + * a) WillChangeXXX will ensure the object is set when we have mutation + * listeners, and + * b) WillChangeXXX will ensure the object represents a serialized version of + * the old attribute value so that the value doesn't change when the + * underlying SVG type is updated. + * + * aNewValue is replaced with the old value. + */ +void SVGElement::DidChangeValue(nsAtom* aName, + const nsAttrValue& aEmptyOrOldValue, + nsAttrValue& aNewValue, + const mozAutoDocUpdate& aProofOfUpdate) { + bool hasListeners = nsContentUtils::HasMutationListeners( + this, NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this); + uint8_t modType = + HasAttr(kNameSpaceID_None, aName) + ? static_cast(MutationEvent_Binding::MODIFICATION) + : static_cast(MutationEvent_Binding::ADDITION); + + // XXX Really, the fourth argument to SetAttrAndNotify should be null if + // aEmptyOrOldValue does not represent the actual previous value of the + // attribute, but currently SVG elements do not even use the old attribute + // value in |AfterSetAttr|, so this should be ok. + SetAttrAndNotify(kNameSpaceID_None, aName, nullptr, &aEmptyOrOldValue, + aNewValue, nullptr, modType, hasListeners, + kNotifyDocumentObservers, kCallAfterSetAttr, + GetComposedDoc(), aProofOfUpdate); +} + +void SVGElement::MaybeSerializeAttrBeforeRemoval(nsAtom* aName, bool aNotify) { + if (!aNotify || !nsContentUtils::HasMutationListeners( + this, NS_EVENT_BITS_MUTATION_ATTRMODIFIED, this)) { + return; + } + + const nsAttrValue* attrValue = mAttrs.GetAttr(aName); + if (!attrValue) return; + + nsAutoString serializedValue; + attrValue->ToString(serializedValue); + nsAttrValue oldAttrValue(serializedValue); + bool oldValueSet; + mAttrs.SetAndSwapAttr(aName, oldAttrValue, &oldValueSet); +} + +nsAtom* SVGElement::GetEventNameForAttr(nsAtom* aAttr) { + if (IsSVGElement(nsGkAtoms::svg)) { + if (aAttr == nsGkAtoms::onload) return nsGkAtoms::onSVGLoad; + if (aAttr == nsGkAtoms::onscroll) return nsGkAtoms::onSVGScroll; + } + if (aAttr == nsGkAtoms::onbegin) return nsGkAtoms::onbeginEvent; + if (aAttr == nsGkAtoms::onrepeat) return nsGkAtoms::onrepeatEvent; + if (aAttr == nsGkAtoms::onend) return nsGkAtoms::onendEvent; + + return SVGElementBase::GetEventNameForAttr(aAttr); +} + +SVGViewportElement* SVGElement::GetCtx() const { + return SVGContentUtils::GetNearestViewportElement(this); +} + +/* virtual */ +gfxMatrix SVGElement::PrependLocalTransformsTo(const gfxMatrix& aMatrix, + SVGTransformTypes aWhich) const { + return aMatrix; +} + +SVGElement::LengthAttributesInfo SVGElement::GetLengthInfo() { + return LengthAttributesInfo(nullptr, nullptr, 0); +} + +void SVGElement::SetLength(nsAtom* aName, const SVGAnimatedLength& aLength) { + LengthAttributesInfo lengthInfo = GetLengthInfo(); + + for (uint32_t i = 0; i < lengthInfo.mCount; i++) { + if (aName == lengthInfo.mInfos[i].mName) { + lengthInfo.mValues[i] = aLength; + DidAnimateLength(i); + return; + } + } + MOZ_ASSERT(false, "no length found to set"); +} + +nsAttrValue SVGElement::WillChangeLength( + uint8_t aAttrEnum, const mozAutoDocUpdate& aProofOfUpdate) { + return WillChangeValue(GetLengthInfo().mInfos[aAttrEnum].mName, + aProofOfUpdate); +} + +void SVGElement::DidChangeLength(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + LengthAttributesInfo info = GetLengthInfo(); + + NS_ASSERTION(info.mCount > 0, + "DidChangeLength on element with no length attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue newValue; + newValue.SetTo(info.mValues[aAttrEnum], nullptr); + + DidChangeValue(info.mInfos[aAttrEnum].mName, aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimateLength(uint8_t aAttrEnum) { + // We need to do this here. Normally the SMIL restyle would also cause us to + // do this from DidSetComputedStyle, but we don't have that guarantee if our + // frame gets reconstructed. + ClearAnyCachedPath(); + + if (SVGGeometryProperty::ElementMapsLengthsToStyle(this)) { + nsCSSPropertyID propId = + SVGGeometryProperty::AttrEnumToCSSPropId(this, aAttrEnum); + + // We don't map use element width/height currently. We can remove this + // test when we do. + if (propId != eCSSProperty_UNKNOWN) { + SMILOverrideStyle()->SetSMILValue(propId, + GetLengthInfo().mValues[aAttrEnum]); + return; + } + } + + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + LengthAttributesInfo info = GetLengthInfo(); + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +SVGAnimatedLength* SVGElement::GetAnimatedLength(uint8_t aAttrEnum) { + LengthAttributesInfo info = GetLengthInfo(); + if (aAttrEnum < info.mCount) { + return &info.mValues[aAttrEnum]; + } + MOZ_ASSERT_UNREACHABLE("Bad attrEnum"); + return nullptr; +} + +SVGAnimatedLength* SVGElement::GetAnimatedLength(const nsAtom* aAttrName) { + LengthAttributesInfo lengthInfo = GetLengthInfo(); + + for (uint32_t i = 0; i < lengthInfo.mCount; i++) { + if (aAttrName == lengthInfo.mInfos[i].mName) { + return &lengthInfo.mValues[i]; + } + } + return nullptr; +} + +void SVGElement::GetAnimatedLengthValues(float* aFirst, ...) { + LengthAttributesInfo info = GetLengthInfo(); + + NS_ASSERTION(info.mCount > 0, + "GetAnimatedLengthValues on element with no length attribs"); + + SVGViewportElement* ctx = nullptr; + + float* f = aFirst; + uint32_t i = 0; + + va_list args; + va_start(args, aFirst); + + while (f && i < info.mCount) { + uint8_t type = info.mValues[i].GetSpecifiedUnitType(); + if (!ctx) { + if (type != SVGLength_Binding::SVG_LENGTHTYPE_NUMBER && + type != SVGLength_Binding::SVG_LENGTHTYPE_PX) + ctx = GetCtx(); + } + if (type == SVGLength_Binding::SVG_LENGTHTYPE_EMS || + type == SVGLength_Binding::SVG_LENGTHTYPE_EXS) + *f = info.mValues[i++].GetAnimValue(this); + else + *f = info.mValues[i++].GetAnimValue(ctx); + f = va_arg(args, float*); + } + + va_end(args); +} + +SVGElement::LengthListAttributesInfo SVGElement::GetLengthListInfo() { + return LengthListAttributesInfo(nullptr, nullptr, 0); +} + +nsAttrValue SVGElement::WillChangeLengthList( + uint8_t aAttrEnum, const mozAutoDocUpdate& aProofOfUpdate) { + return WillChangeValue(GetLengthListInfo().mInfos[aAttrEnum].mName, + aProofOfUpdate); +} + +void SVGElement::DidChangeLengthList(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + LengthListAttributesInfo info = GetLengthListInfo(); + + NS_ASSERTION(info.mCount > 0, + "DidChangeLengthList on element with no length list attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue newValue; + newValue.SetTo(info.mValues[aAttrEnum].GetBaseValue(), nullptr); + + DidChangeValue(info.mInfos[aAttrEnum].mName, aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimateLengthList(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + LengthListAttributesInfo info = GetLengthListInfo(); + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +void SVGElement::GetAnimatedLengthListValues(SVGUserUnitList* aFirst, ...) { + LengthListAttributesInfo info = GetLengthListInfo(); + + NS_ASSERTION( + info.mCount > 0, + "GetAnimatedLengthListValues on element with no length list attribs"); + + SVGUserUnitList* list = aFirst; + uint32_t i = 0; + + va_list args; + va_start(args, aFirst); + + while (list && i < info.mCount) { + list->Init(&(info.mValues[i].GetAnimValue()), this, info.mInfos[i].mAxis); + ++i; + list = va_arg(args, SVGUserUnitList*); + } + + va_end(args); +} + +SVGAnimatedLengthList* SVGElement::GetAnimatedLengthList(uint8_t aAttrEnum) { + LengthListAttributesInfo info = GetLengthListInfo(); + if (aAttrEnum < info.mCount) { + return &(info.mValues[aAttrEnum]); + } + MOZ_ASSERT_UNREACHABLE("Bad attrEnum"); + return nullptr; +} + +SVGElement::NumberListAttributesInfo SVGElement::GetNumberListInfo() { + return NumberListAttributesInfo(nullptr, nullptr, 0); +} + +nsAttrValue SVGElement::WillChangeNumberList( + uint8_t aAttrEnum, const mozAutoDocUpdate& aProofOfUpdate) { + return WillChangeValue(GetNumberListInfo().mInfos[aAttrEnum].mName, + aProofOfUpdate); +} + +void SVGElement::DidChangeNumberList(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + NumberListAttributesInfo info = GetNumberListInfo(); + + MOZ_ASSERT(info.mCount > 0, + "DidChangeNumberList on element with no number list attribs"); + MOZ_ASSERT(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue newValue; + newValue.SetTo(info.mValues[aAttrEnum].GetBaseValue(), nullptr); + + DidChangeValue(info.mInfos[aAttrEnum].mName, aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimateNumberList(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + NumberListAttributesInfo info = GetNumberListInfo(); + MOZ_ASSERT(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +SVGAnimatedNumberList* SVGElement::GetAnimatedNumberList(uint8_t aAttrEnum) { + NumberListAttributesInfo info = GetNumberListInfo(); + if (aAttrEnum < info.mCount) { + return &(info.mValues[aAttrEnum]); + } + MOZ_ASSERT(false, "Bad attrEnum"); + return nullptr; +} + +SVGAnimatedNumberList* SVGElement::GetAnimatedNumberList(nsAtom* aAttrName) { + NumberListAttributesInfo info = GetNumberListInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aAttrName == info.mInfos[i].mName) { + return &info.mValues[i]; + } + } + MOZ_ASSERT(false, "Bad caller"); + return nullptr; +} + +nsAttrValue SVGElement::WillChangePointList( + const mozAutoDocUpdate& aProofOfUpdate) { + MOZ_ASSERT(GetPointListAttrName(), "Changing non-existent point list?"); + return WillChangeValue(GetPointListAttrName(), aProofOfUpdate); +} + +void SVGElement::DidChangePointList(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + MOZ_ASSERT(GetPointListAttrName(), "Changing non-existent point list?"); + + nsAttrValue newValue; + newValue.SetTo(GetAnimatedPointList()->GetBaseValue(), nullptr); + + DidChangeValue(GetPointListAttrName(), aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimatePointList() { + MOZ_ASSERT(GetPointListAttrName(), "Animating non-existent path data?"); + + ClearAnyCachedPath(); + + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + frame->AttributeChanged(kNameSpaceID_None, GetPointListAttrName(), + MutationEvent_Binding::SMIL); + } +} + +nsAttrValue SVGElement::WillChangePathSegList( + const mozAutoDocUpdate& aProofOfUpdate) { + MOZ_ASSERT(GetPathDataAttrName(), "Changing non-existent path seg list?"); + return WillChangeValue(GetPathDataAttrName(), aProofOfUpdate); +} + +void SVGElement::DidChangePathSegList(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + MOZ_ASSERT(GetPathDataAttrName(), "Changing non-existent path seg list?"); + + nsAttrValue newValue; + newValue.SetTo(GetAnimPathSegList()->GetBaseValue(), nullptr); + + DidChangeValue(GetPathDataAttrName(), aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimatePathSegList() { + nsStaticAtom* name = GetPathDataAttrName(); + MOZ_ASSERT(name, "Animating non-existent path data?"); + + ClearAnyCachedPath(); + + // Notify style we have to update the d property because of SMIL animation. + if (name == nsGkAtoms::d) { + SMILOverrideStyle()->SetSMILValue(nsCSSPropertyID::eCSSProperty_d, + *GetAnimPathSegList()); + return; + } + + if (nsIFrame* frame = GetPrimaryFrame()) { + frame->AttributeChanged(kNameSpaceID_None, name, + MutationEvent_Binding::SMIL); + } +} + +SVGElement::NumberAttributesInfo SVGElement::GetNumberInfo() { + return NumberAttributesInfo(nullptr, nullptr, 0); +} + +void SVGElement::DidChangeNumber(uint8_t aAttrEnum) { + NumberAttributesInfo info = GetNumberInfo(); + + NS_ASSERTION(info.mCount > 0, + "DidChangeNumber on element with no number attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue attrValue; + attrValue.SetTo(info.mValues[aAttrEnum].GetBaseValue(), nullptr); + + SetParsedAttr(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, nullptr, + attrValue, true); +} + +void SVGElement::DidAnimateNumber(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + NumberAttributesInfo info = GetNumberInfo(); + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +void SVGElement::GetAnimatedNumberValues(float* aFirst, ...) { + NumberAttributesInfo info = GetNumberInfo(); + + NS_ASSERTION(info.mCount > 0, + "GetAnimatedNumberValues on element with no number attribs"); + + float* f = aFirst; + uint32_t i = 0; + + va_list args; + va_start(args, aFirst); + + while (f && i < info.mCount) { + *f = info.mValues[i++].GetAnimValue(); + f = va_arg(args, float*); + } + va_end(args); +} + +SVGElement::NumberPairAttributesInfo SVGElement::GetNumberPairInfo() { + return NumberPairAttributesInfo(nullptr, nullptr, 0); +} + +nsAttrValue SVGElement::WillChangeNumberPair(uint8_t aAttrEnum) { + mozAutoDocUpdate updateBatch(GetComposedDoc(), kDontNotifyDocumentObservers); + return WillChangeValue(GetNumberPairInfo().mInfos[aAttrEnum].mName, + updateBatch); +} + +void SVGElement::DidChangeNumberPair(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue) { + NumberPairAttributesInfo info = GetNumberPairInfo(); + + NS_ASSERTION(info.mCount > 0, + "DidChangePairNumber on element with no number pair attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue newValue; + newValue.SetTo(info.mValues[aAttrEnum], nullptr); + + mozAutoDocUpdate updateBatch(GetComposedDoc(), kNotifyDocumentObservers); + DidChangeValue(info.mInfos[aAttrEnum].mName, aEmptyOrOldValue, newValue, + updateBatch); +} + +void SVGElement::DidAnimateNumberPair(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + NumberPairAttributesInfo info = GetNumberPairInfo(); + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +SVGElement::IntegerAttributesInfo SVGElement::GetIntegerInfo() { + return IntegerAttributesInfo(nullptr, nullptr, 0); +} + +void SVGElement::DidChangeInteger(uint8_t aAttrEnum) { + IntegerAttributesInfo info = GetIntegerInfo(); + NS_ASSERTION(info.mCount > 0, + "DidChangeInteger on element with no integer attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue attrValue; + attrValue.SetTo(info.mValues[aAttrEnum].GetBaseValue(), nullptr); + + SetParsedAttr(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, nullptr, + attrValue, true); +} + +void SVGElement::DidAnimateInteger(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + IntegerAttributesInfo info = GetIntegerInfo(); + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +void SVGElement::GetAnimatedIntegerValues(int32_t* aFirst, ...) { + IntegerAttributesInfo info = GetIntegerInfo(); + + NS_ASSERTION(info.mCount > 0, + "GetAnimatedIntegerValues on element with no integer attribs"); + + int32_t* n = aFirst; + uint32_t i = 0; + + va_list args; + va_start(args, aFirst); + + while (n && i < info.mCount) { + *n = info.mValues[i++].GetAnimValue(); + n = va_arg(args, int32_t*); + } + va_end(args); +} + +SVGElement::IntegerPairAttributesInfo SVGElement::GetIntegerPairInfo() { + return IntegerPairAttributesInfo(nullptr, nullptr, 0); +} + +nsAttrValue SVGElement::WillChangeIntegerPair( + uint8_t aAttrEnum, const mozAutoDocUpdate& aProofOfUpdate) { + return WillChangeValue(GetIntegerPairInfo().mInfos[aAttrEnum].mName, + aProofOfUpdate); +} + +void SVGElement::DidChangeIntegerPair(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + IntegerPairAttributesInfo info = GetIntegerPairInfo(); + + NS_ASSERTION(info.mCount > 0, + "DidChangeIntegerPair on element with no integer pair attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue newValue; + newValue.SetTo(info.mValues[aAttrEnum], nullptr); + + DidChangeValue(info.mInfos[aAttrEnum].mName, aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimateIntegerPair(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + IntegerPairAttributesInfo info = GetIntegerPairInfo(); + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +SVGElement::BooleanAttributesInfo SVGElement::GetBooleanInfo() { + return BooleanAttributesInfo(nullptr, nullptr, 0); +} + +void SVGElement::DidChangeBoolean(uint8_t aAttrEnum) { + BooleanAttributesInfo info = GetBooleanInfo(); + + NS_ASSERTION(info.mCount > 0, + "DidChangeBoolean on element with no boolean attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue attrValue(info.mValues[aAttrEnum].GetBaseValueAtom()); + SetParsedAttr(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, nullptr, + attrValue, true); +} + +void SVGElement::DidAnimateBoolean(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + BooleanAttributesInfo info = GetBooleanInfo(); + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +SVGElement::EnumAttributesInfo SVGElement::GetEnumInfo() { + return EnumAttributesInfo(nullptr, nullptr, 0); +} + +void SVGElement::DidChangeEnum(uint8_t aAttrEnum) { + EnumAttributesInfo info = GetEnumInfo(); + + NS_ASSERTION(info.mCount > 0, + "DidChangeEnum on element with no enum attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + nsAttrValue attrValue(info.mValues[aAttrEnum].GetBaseValueAtom(this)); + SetParsedAttr(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, nullptr, + attrValue, true); +} + +void SVGElement::DidAnimateEnum(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + EnumAttributesInfo info = GetEnumInfo(); + frame->AttributeChanged(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +SVGAnimatedOrient* SVGElement::GetAnimatedOrient() { return nullptr; } + +nsAttrValue SVGElement::WillChangeOrient( + const mozAutoDocUpdate& aProofOfUpdate) { + return WillChangeValue(nsGkAtoms::orient, aProofOfUpdate); +} + +void SVGElement::DidChangeOrient(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + SVGAnimatedOrient* orient = GetAnimatedOrient(); + + NS_ASSERTION(orient, "DidChangeOrient on element with no orient attrib"); + + nsAttrValue newValue; + newValue.SetTo(*orient, nullptr); + + DidChangeValue(nsGkAtoms::orient, aEmptyOrOldValue, newValue, aProofOfUpdate); +} + +void SVGElement::DidAnimateOrient() { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + frame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::orient, + MutationEvent_Binding::SMIL); + } +} + +SVGAnimatedViewBox* SVGElement::GetAnimatedViewBox() { return nullptr; } + +nsAttrValue SVGElement::WillChangeViewBox( + const mozAutoDocUpdate& aProofOfUpdate) { + return WillChangeValue(nsGkAtoms::viewBox, aProofOfUpdate); +} + +void SVGElement::DidChangeViewBox(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + SVGAnimatedViewBox* viewBox = GetAnimatedViewBox(); + + NS_ASSERTION(viewBox, "DidChangeViewBox on element with no viewBox attrib"); + + nsAttrValue newValue; + newValue.SetTo(*viewBox, nullptr); + + DidChangeValue(nsGkAtoms::viewBox, aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimateViewBox() { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + frame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::viewBox, + MutationEvent_Binding::SMIL); + } +} + +SVGAnimatedPreserveAspectRatio* SVGElement::GetAnimatedPreserveAspectRatio() { + return nullptr; +} + +nsAttrValue SVGElement::WillChangePreserveAspectRatio( + const mozAutoDocUpdate& aProofOfUpdate) { + return WillChangeValue(nsGkAtoms::preserveAspectRatio, aProofOfUpdate); +} + +void SVGElement::DidChangePreserveAspectRatio( + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + SVGAnimatedPreserveAspectRatio* preserveAspectRatio = + GetAnimatedPreserveAspectRatio(); + + NS_ASSERTION(preserveAspectRatio, + "DidChangePreserveAspectRatio on element with no " + "preserveAspectRatio attrib"); + + nsAttrValue newValue; + newValue.SetTo(*preserveAspectRatio, nullptr); + + DidChangeValue(nsGkAtoms::preserveAspectRatio, aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimatePreserveAspectRatio() { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + frame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::preserveAspectRatio, + MutationEvent_Binding::SMIL); + } +} + +nsAttrValue SVGElement::WillChangeTransformList( + const mozAutoDocUpdate& aProofOfUpdate) { + return WillChangeValue(GetTransformListAttrName(), aProofOfUpdate); +} + +void SVGElement::DidChangeTransformList( + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + MOZ_ASSERT(GetTransformListAttrName(), + "Changing non-existent transform list?"); + + // The transform attribute is being set, so we must ensure that the + // SVGAnimatedTransformList is/has been allocated: + nsAttrValue newValue; + newValue.SetTo(GetAnimatedTransformList(DO_ALLOCATE)->GetBaseValue(), + nullptr); + + DidChangeValue(GetTransformListAttrName(), aEmptyOrOldValue, newValue, + aProofOfUpdate); +} + +void SVGElement::DidAnimateTransformList(int32_t aModType) { + MOZ_ASSERT(GetTransformListAttrName(), + "Animating non-existent transform data?"); + + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + nsAtom* transformAttr = GetTransformListAttrName(); + frame->AttributeChanged(kNameSpaceID_None, transformAttr, aModType); + // When script changes the 'transform' attribute, Element::SetAttrAndNotify + // will call MutationObservers::NotifyAttributeChanged, under which + // SVGTransformableElement::GetAttributeChangeHint will be called and an + // appropriate change event posted to update our frame's overflow rects. + // The SetAttrAndNotify doesn't happen for transform changes caused by + // 'animateTransform' though (and sending out the mutation events that + // MutationObservers::NotifyAttributeChanged dispatches would be + // inappropriate anyway), so we need to post the change event ourself. + nsChangeHint changeHint = GetAttributeChangeHint(transformAttr, aModType); + if (changeHint) { + nsLayoutUtils::PostRestyleEvent(this, RestyleHint{0}, changeHint); + } + } +} + +SVGElement::StringAttributesInfo SVGElement::GetStringInfo() { + return StringAttributesInfo(nullptr, nullptr, 0); +} + +void SVGElement::GetStringBaseValue(uint8_t aAttrEnum, + nsAString& aResult) const { + SVGElement::StringAttributesInfo info = + const_cast(this)->GetStringInfo(); + + NS_ASSERTION(info.mCount > 0, + "GetBaseValue on element with no string attribs"); + + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + GetAttr(info.mInfos[aAttrEnum].mNamespaceID, info.mInfos[aAttrEnum].mName, + aResult); +} + +void SVGElement::SetStringBaseValue(uint8_t aAttrEnum, + const nsAString& aValue) { + SVGElement::StringAttributesInfo info = GetStringInfo(); + + NS_ASSERTION(info.mCount > 0, + "SetBaseValue on element with no string attribs"); + + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + SetAttr(info.mInfos[aAttrEnum].mNamespaceID, info.mInfos[aAttrEnum].mName, + aValue, true); +} + +void SVGElement::DidAnimateString(uint8_t aAttrEnum) { + nsIFrame* frame = GetPrimaryFrame(); + + if (frame) { + StringAttributesInfo info = GetStringInfo(); + frame->AttributeChanged(info.mInfos[aAttrEnum].mNamespaceID, + info.mInfos[aAttrEnum].mName, + MutationEvent_Binding::SMIL); + } +} + +SVGElement::StringListAttributesInfo SVGElement::GetStringListInfo() { + return StringListAttributesInfo(nullptr, nullptr, 0); +} + +nsAttrValue SVGElement::WillChangeStringList( + bool aIsConditionalProcessingAttribute, uint8_t aAttrEnum, + const mozAutoDocUpdate& aProofOfUpdate) { + nsStaticAtom* name; + if (aIsConditionalProcessingAttribute) { + nsCOMPtr tests(do_QueryInterface(this)); + name = tests->GetAttrName(aAttrEnum); + } else { + name = GetStringListInfo().mInfos[aAttrEnum].mName; + } + return WillChangeValue(name, aProofOfUpdate); +} + +void SVGElement::DidChangeStringList(bool aIsConditionalProcessingAttribute, + uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate) { + nsStaticAtom* name; + nsAttrValue newValue; + nsCOMPtr tests; + + if (aIsConditionalProcessingAttribute) { + tests = do_QueryObject(this); + name = tests->GetAttrName(aAttrEnum); + tests->GetAttrValue(aAttrEnum, newValue); + } else { + StringListAttributesInfo info = GetStringListInfo(); + + NS_ASSERTION(info.mCount > 0, + "DidChangeStringList on element with no string list attribs"); + NS_ASSERTION(aAttrEnum < info.mCount, "aAttrEnum out of range"); + + name = info.mInfos[aAttrEnum].mName; + newValue.SetTo(info.mValues[aAttrEnum], nullptr); + } + + DidChangeValue(name, aEmptyOrOldValue, newValue, aProofOfUpdate); + + if (aIsConditionalProcessingAttribute) { + tests->MaybeInvalidate(); + } +} + +nsresult SVGElement::ReportAttributeParseFailure(Document* aDocument, + nsAtom* aAttribute, + const nsAString& aValue) { + AutoTArray strings; + strings.AppendElement(nsDependentAtomString(aAttribute)); + strings.AppendElement(aValue); + return SVGContentUtils::ReportToConsole(aDocument, "AttributeParseWarning", + strings); +} + +UniquePtr SVGElement::GetAnimatedAttr(int32_t aNamespaceID, + nsAtom* aName) { + if (aNamespaceID == kNameSpaceID_None) { + // Transforms: + if (GetTransformListAttrName() == aName) { + // The transform attribute is being animated, so we must ensure that the + // SVGAnimatedTransformList is/has been allocated: + return GetAnimatedTransformList(DO_ALLOCATE)->ToSMILAttr(this); + } + + // Motion (fake 'attribute' for animateMotion) + if (aName == nsGkAtoms::mozAnimateMotionDummyAttr) { + return MakeUnique(this); + } + + // Lengths: + LengthAttributesInfo info = GetLengthInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return info.mValues[i].ToSMILAttr(this); + } + } + + // Numbers: + { + NumberAttributesInfo info = GetNumberInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return info.mValues[i].ToSMILAttr(this); + } + } + } + + // Number Pairs: + { + NumberPairAttributesInfo info = GetNumberPairInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return info.mValues[i].ToSMILAttr(this); + } + } + } + + // Integers: + { + IntegerAttributesInfo info = GetIntegerInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return info.mValues[i].ToSMILAttr(this); + } + } + } + + // Integer Pairs: + { + IntegerPairAttributesInfo info = GetIntegerPairInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return info.mValues[i].ToSMILAttr(this); + } + } + } + + // Enumerations: + { + EnumAttributesInfo info = GetEnumInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return info.mValues[i].ToSMILAttr(this); + } + } + } + + // Booleans: + { + BooleanAttributesInfo info = GetBooleanInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return info.mValues[i].ToSMILAttr(this); + } + } + } + + // orient: + if (aName == nsGkAtoms::orient) { + SVGAnimatedOrient* orient = GetAnimatedOrient(); + return orient ? orient->ToSMILAttr(this) : nullptr; + } + + // viewBox: + if (aName == nsGkAtoms::viewBox) { + SVGAnimatedViewBox* viewBox = GetAnimatedViewBox(); + return viewBox ? viewBox->ToSMILAttr(this) : nullptr; + } + + // preserveAspectRatio: + if (aName == nsGkAtoms::preserveAspectRatio) { + SVGAnimatedPreserveAspectRatio* preserveAspectRatio = + GetAnimatedPreserveAspectRatio(); + return preserveAspectRatio ? preserveAspectRatio->ToSMILAttr(this) + : nullptr; + } + + // NumberLists: + { + NumberListAttributesInfo info = GetNumberListInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + MOZ_ASSERT(i <= UCHAR_MAX, "Too many attributes"); + return info.mValues[i].ToSMILAttr(this, uint8_t(i)); + } + } + } + + // LengthLists: + { + LengthListAttributesInfo info = GetLengthListInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + MOZ_ASSERT(i <= UCHAR_MAX, "Too many attributes"); + return info.mValues[i].ToSMILAttr(this, uint8_t(i), + info.mInfos[i].mAxis, + info.mInfos[i].mCouldZeroPadList); + } + } + } + + // PointLists: + { + if (GetPointListAttrName() == aName) { + SVGAnimatedPointList* pointList = GetAnimatedPointList(); + if (pointList) { + return pointList->ToSMILAttr(this); + } + } + } + + // PathSegLists: + { + if (GetPathDataAttrName() == aName) { + SVGAnimatedPathSegList* segList = GetAnimPathSegList(); + if (segList) { + return segList->ToSMILAttr(this); + } + } + } + + if (aName == nsGkAtoms::_class) { + return mClassAttribute.ToSMILAttr(this); + } + } + + // Strings + { + StringAttributesInfo info = GetStringInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aNamespaceID == info.mInfos[i].mNamespaceID && + aName == info.mInfos[i].mName) { + return info.mValues[i].ToSMILAttr(this); + } + } + } + + return nullptr; +} + +void SVGElement::AnimationNeedsResample() { + Document* doc = GetComposedDoc(); + if (doc && doc->HasAnimationController()) { + doc->GetAnimationController()->SetResampleNeeded(); + } +} + +void SVGElement::FlushAnimations() { + Document* doc = GetComposedDoc(); + if (doc && doc->HasAnimationController()) { + doc->GetAnimationController()->FlushResampleRequests(); + } +} + +void SVGElement::AddSizeOfExcludingThis(nsWindowSizes& aSizes, + size_t* aNodeSize) const { + Element::AddSizeOfExcludingThis(aSizes, aNodeSize); + + // These are owned by the element and not referenced from the stylesheets. + // They're referenced from the rule tree, but the rule nodes don't measure + // their style source (since they're non-owning), so unconditionally reporting + // them even though it's a refcounted object is ok. + if (mContentDeclarationBlock) { + aSizes.mLayoutSvgMappedDeclarations += + mContentDeclarationBlock->SizeofIncludingThis( + aSizes.mState.mMallocSizeOf); + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGElement.h b/dom/svg/SVGElement.h new file mode 100644 index 0000000000..f062194eb3 --- /dev/null +++ b/dom/svg/SVGElement.h @@ -0,0 +1,590 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGELEMENT_H_ +#define DOM_SVG_SVGELEMENT_H_ + +/* + SVGElement is the base class for all SVG content elements. + It implements all the common DOM interfaces and handles attributes. +*/ + +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SVGAnimatedClass.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/dom/DOMRect.h" +#include "mozilla/dom/Element.h" +#include "mozilla/gfx/MatrixFwd.h" +#include "mozilla/UniquePtr.h" +#include "nsCSSPropertyID.h" +#include "nsChangeHint.h" +#include "nsCycleCollectionParticipant.h" +#include "nsError.h" +#include "nsISupportsImpl.h" +#include "nsStyledElement.h" +#include "gfxMatrix.h" + +// {70db954d-e452-4be3-83aa-f54a51cf7890} +#define MOZILLA_SVGELEMENT_IID \ + { \ + 0x70db954d, 0xe452, 0x4be3, { \ + 0x82, 0xaa, 0xf5, 0x4a, 0x51, 0xcf, 0x78, 0x90 \ + } \ + } + +nsresult NS_NewSVGElement(mozilla::dom::Element** aResult, + already_AddRefed&& aNodeInfo); + +class mozAutoDocUpdate; + +namespace mozilla { +class DeclarationBlock; + +class SVGAnimatedBoolean; +class SVGAnimatedEnumeration; +class SVGAnimatedInteger; +class SVGAnimatedIntegerPair; +class SVGAnimatedLength; +class SVGAnimatedLengthList; +class SVGAnimatedNumber; +class SVGAnimatedNumberList; +class SVGAnimatedNumberPair; +class SVGAnimatedOrient; +class SVGAnimatedPathSegList; +class SVGAnimatedPointList; +class SVGAnimatedString; +class SVGAnimatedPreserveAspectRatio; +class SVGAnimatedTransformList; +class SVGAnimatedViewBox; +class SVGNumberList; +class SVGStringList; +class SVGUserUnitList; + +struct SVGEnumMapping; + +namespace dom { +class DOMSVGStringList; +class SVGSVGElement; +class SVGViewportElement; + +using SVGElementBase = nsStyledElement; + +class SVGElement : public SVGElementBase // nsIContent +{ + protected: + explicit SVGElement(already_AddRefed&& aNodeInfo); + friend nsresult( + ::NS_NewSVGElement(mozilla::dom::Element** aResult, + already_AddRefed&& aNodeInfo)); + nsresult Init(); + virtual ~SVGElement(); + + public: + nsresult Clone(mozilla::dom::NodeInfo*, + nsINode** aResult) const MOZ_MUST_OVERRIDE override; + + // From Element + nsresult CopyInnerTo(mozilla::dom::Element* aDest); + + NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_SVGELEMENT_IID) + // nsISupports + NS_INLINE_DECL_REFCOUNTING_INHERITED(SVGElement, SVGElementBase) + + NS_DECL_ADDSIZEOFEXCLUDINGTHIS + + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; + + void DidAnimateClass(); + + void SetNonce(const nsAString& aNonce) { + SetProperty(nsGkAtoms::nonce, new nsString(aNonce), + nsINode::DeleteProperty, /* aTransfer = */ true); + } + void RemoveNonce() { RemoveProperty(nsGkAtoms::nonce); } + void GetNonce(nsAString& aNonce) const { + nsString* cspNonce = static_cast(GetProperty(nsGkAtoms::nonce)); + if (cspNonce) { + aNonce = *cspNonce; + } + } + + // nsIContent interface methods + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + + nsChangeHint GetAttributeChangeHint(const nsAtom* aAttribute, + int32_t aModType) const override; + + /** + * We override the default to unschedule computation of Servo declaration + * blocks when adopted across documents. + */ + void NodeInfoChanged(Document* aOldDoc) override; + + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + NS_IMPL_FROMNODE(SVGElement, kNameSpaceID_SVG) + + // Gets the element that establishes the rectangular viewport against which + // we should resolve percentage lengths (our "coordinate context"). Returns + // nullptr for outer or SVG without an parent (invalid SVG). + mozilla::dom::SVGViewportElement* GetCtx() const; + + /** + * Returns aMatrix pre-multiplied by (explicit or implicit) transforms that + * are introduced by attributes on this element. + * + * If aWhich is eAllTransforms, then all the transforms from the coordinate + * space established by this element for its children to the coordinate + * space established by this element's parent element for this element, are + * included. + * + * If aWhich is eUserSpaceToParent, then only the transforms from this + * element's userspace to the coordinate space established by its parent is + * included. This includes any transforms introduced by the 'transform' + * attribute, transform animations and animateMotion, but not any offsets + * due to e.g. 'x'/'y' attributes, or any transform due to a 'viewBox' + * attribute. (SVG userspace is defined to be the coordinate space in which + * coordinates on an element apply.) + * + * If aWhich is eChildToUserSpace, then only the transforms from the + * coordinate space established by this element for its childre to this + * elements userspace are included. This includes any offsets due to e.g. + * 'x'/'y' attributes, and any transform due to a 'viewBox' attribute, but + * does not include any transforms due to the 'transform' attribute. + */ + virtual gfxMatrix PrependLocalTransformsTo( + const gfxMatrix& aMatrix, + SVGTransformTypes aWhich = eAllTransforms) const; + + // Setter for to set the current transformation + // Only visible for SVGGraphicElement, so it's a no-op here, and that + // subclass has the useful implementation. + virtual void SetAnimateMotionTransform( + const mozilla::gfx::Matrix* aMatrix) { /*no-op*/ + } + virtual const mozilla::gfx::Matrix* GetAnimateMotionTransform() const { + return nullptr; + } + + bool IsStringAnimatable(uint8_t aAttrEnum) { + return GetStringInfo().mInfos[aAttrEnum].mIsAnimatable; + } + bool NumberAttrAllowsPercentage(uint8_t aAttrEnum) { + return GetNumberInfo().mInfos[aAttrEnum].mPercentagesAllowed; + } + virtual bool HasValidDimensions() const { return true; } + void SetLength(nsAtom* aName, const SVGAnimatedLength& aLength); + + enum class ValToUse { Base, Anim }; + static bool UpdateDeclarationBlockFromLength(DeclarationBlock& aBlock, + nsCSSPropertyID aPropId, + const SVGAnimatedLength& aLength, + ValToUse aValToUse); + static bool UpdateDeclarationBlockFromPath( + DeclarationBlock& aBlock, const SVGAnimatedPathSegList& aPath, + ValToUse aValToUse); + + nsAttrValue WillChangeLength(uint8_t aAttrEnum, + const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangeNumberPair(uint8_t aAttrEnum); + nsAttrValue WillChangeIntegerPair(uint8_t aAttrEnum, + const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangeOrient(const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangeViewBox(const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangePreserveAspectRatio( + const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangeNumberList(uint8_t aAttrEnum, + const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangeLengthList(uint8_t aAttrEnum, + const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangePointList(const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangePathSegList(const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangeTransformList(const mozAutoDocUpdate& aProofOfUpdate); + nsAttrValue WillChangeStringList(bool aIsConditionalProcessingAttribute, + uint8_t aAttrEnum, + const mozAutoDocUpdate& aProofOfUpdate); + + void DidChangeLength(uint8_t aAttrEnum, const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangeNumber(uint8_t aAttrEnum); + void DidChangeNumberPair(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue); + void DidChangeInteger(uint8_t aAttrEnum); + void DidChangeIntegerPair(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangeBoolean(uint8_t aAttrEnum); + void DidChangeEnum(uint8_t aAttrEnum); + void DidChangeOrient(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangeViewBox(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangePreserveAspectRatio(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangeNumberList(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangeLengthList(uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangePointList(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangePathSegList(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangeTransformList(const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + void DidChangeString(uint8_t aAttrEnum) {} + void DidChangeStringList(bool aIsConditionalProcessingAttribute, + uint8_t aAttrEnum, + const nsAttrValue& aEmptyOrOldValue, + const mozAutoDocUpdate& aProofOfUpdate); + + void DidAnimateLength(uint8_t aAttrEnum); + void DidAnimateNumber(uint8_t aAttrEnum); + void DidAnimateNumberPair(uint8_t aAttrEnum); + void DidAnimateInteger(uint8_t aAttrEnum); + void DidAnimateIntegerPair(uint8_t aAttrEnum); + void DidAnimateBoolean(uint8_t aAttrEnum); + void DidAnimateEnum(uint8_t aAttrEnum); + void DidAnimateOrient(); + void DidAnimateViewBox(); + void DidAnimatePreserveAspectRatio(); + void DidAnimateNumberList(uint8_t aAttrEnum); + void DidAnimateLengthList(uint8_t aAttrEnum); + void DidAnimatePointList(); + void DidAnimatePathSegList(); + void DidAnimateTransformList(int32_t aModType); + void DidAnimateString(uint8_t aAttrEnum); + + enum { + /** + * Flag to indicate to GetAnimatedXxx() methods that the object being + * requested should be allocated if it hasn't already been allocated, and + * that the method should not return null. Only applicable to methods that + * need to allocate the object that they return. + */ + DO_ALLOCATE = 0x1 + }; + + SVGAnimatedLength* GetAnimatedLength(uint8_t aAttrEnum); + SVGAnimatedLength* GetAnimatedLength(const nsAtom* aAttrName); + void GetAnimatedLengthValues(float* aFirst, ...); + void GetAnimatedNumberValues(float* aFirst, ...); + void GetAnimatedIntegerValues(int32_t* aFirst, ...); + SVGAnimatedNumberList* GetAnimatedNumberList(uint8_t aAttrEnum); + SVGAnimatedNumberList* GetAnimatedNumberList(nsAtom* aAttrName); + void GetAnimatedLengthListValues(SVGUserUnitList* aFirst, ...); + SVGAnimatedLengthList* GetAnimatedLengthList(uint8_t aAttrEnum); + virtual SVGAnimatedPointList* GetAnimatedPointList() { return nullptr; } + virtual SVGAnimatedPathSegList* GetAnimPathSegList() { + // DOM interface 'SVGAnimatedPathData' (*inherited* by SVGPathElement) + // has a member called 'animatedPathSegList' member, so we have a shorter + // name so we don't get hidden by the GetAnimatedPathSegList declared by + // NS_DECL_NSIDOMSVGANIMATEDPATHDATA. + return nullptr; + } + /** + * Get the SVGAnimatedTransformList for this element. + * + * Despite the fact that animated transform lists are used for a variety of + * attributes, no SVG element uses more than one. + * + * It's relatively uncommon for elements to have their transform attribute + * set, so to save memory the SVGAnimatedTransformList is not allocated + * until the attribute is set/animated or its DOM wrapper is created. Callers + * that require the SVGAnimatedTransformList to be allocated and for this + * method to return non-null must pass the DO_ALLOCATE flag. + */ + virtual SVGAnimatedTransformList* GetAnimatedTransformList( + uint32_t aFlags = 0) { + return nullptr; + } + + mozilla::UniquePtr GetAnimatedAttr(int32_t aNamespaceID, + nsAtom* aName) override; + void AnimationNeedsResample(); + void FlushAnimations(); + + void GetStringBaseValue(uint8_t aAttrEnum, nsAString& aResult) const; + void SetStringBaseValue(uint8_t aAttrEnum, const nsAString& aValue); + + virtual nsStaticAtom* GetPointListAttrName() const { return nullptr; } + virtual nsStaticAtom* GetPathDataAttrName() const { return nullptr; } + virtual nsStaticAtom* GetTransformListAttrName() const { return nullptr; } + const nsAttrValue* GetAnimatedClassName() const { + if (!mClassAttribute.IsAnimated()) { + return nullptr; + } + return mClassAnimAttr.get(); + } + + virtual void ClearAnyCachedPath() {} + virtual bool IsTransformable() { return false; } + + // WebIDL + mozilla::dom::SVGSVGElement* GetOwnerSVGElement(); + SVGElement* GetViewportElement(); + already_AddRefed ClassName(); + + void UpdateContentDeclarationBlock(); + const mozilla::DeclarationBlock* GetContentDeclarationBlock() const; + + bool Autofocus() const { return GetBoolAttr(nsGkAtoms::autofocus); } + void SetAutofocus(bool aAutofocus, ErrorResult& aRv) { + if (aAutofocus) { + SetAttr(nsGkAtoms::autofocus, u""_ns, aRv); + } else { + UnsetAttr(nsGkAtoms::autofocus, aRv); + } + } + + protected: + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + + // We define BeforeSetAttr here and mark it final to ensure it is NOT used + // by SVG elements. + // This is because we're not currently passing the correct value for aValue to + // BeforeSetAttr since it would involve allocating extra SVG value types. + // See the comment in SVGElement::WillChangeValue. + void BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, bool aNotify) final; + void AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, bool aNotify) override; + bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) override; + static nsresult ReportAttributeParseFailure(Document* aDocument, + nsAtom* aAttribute, + const nsAString& aValue); + + nsAttrValue WillChangeValue(nsAtom* aName, + const mozAutoDocUpdate& aProofOfUpdate); + // aNewValue is set to the old value. This value may be invalid if + // !StoresOwnData. + void DidChangeValue(nsAtom* aName, const nsAttrValue& aEmptyOrOldValue, + nsAttrValue& aNewValue, + const mozAutoDocUpdate& aProofOfUpdate); + void MaybeSerializeAttrBeforeRemoval(nsAtom* aName, bool aNotify); + + nsAtom* GetEventNameForAttr(nsAtom* aAttr) override; + + struct LengthInfo { + nsStaticAtom* const mName; + const float mDefaultValue; + const uint8_t mDefaultUnitType; + const uint8_t mCtxType; + }; + + template + struct AttributesInfo { + Value* const mValues; + const InfoValue* const mInfos; + const uint32_t mCount; + + AttributesInfo(Value* aValues, const InfoValue* aInfos, uint32_t aCount) + : mValues(aValues), mInfos(aInfos), mCount(aCount) {} + + void CopyAllFrom(const AttributesInfo&); + void ResetAll(); + void Reset(uint8_t aEnum); + }; + + using LengthAttributesInfo = AttributesInfo; + + struct NumberInfo { + nsStaticAtom* const mName; + const float mDefaultValue; + const bool mPercentagesAllowed; + }; + + using NumberAttributesInfo = AttributesInfo; + + struct NumberPairInfo { + nsStaticAtom* const mName; + const float mDefaultValue1; + const float mDefaultValue2; + }; + + using NumberPairAttributesInfo = + AttributesInfo; + + struct IntegerInfo { + nsStaticAtom* const mName; + const int32_t mDefaultValue; + }; + + using IntegerAttributesInfo = AttributesInfo; + + struct IntegerPairInfo { + nsStaticAtom* const mName; + const int32_t mDefaultValue1; + const int32_t mDefaultValue2; + }; + + using IntegerPairAttributesInfo = + AttributesInfo; + + struct BooleanInfo { + nsStaticAtom* const mName; + const bool mDefaultValue; + }; + + using BooleanAttributesInfo = AttributesInfo; + + friend class mozilla::SVGAnimatedEnumeration; + + struct EnumInfo { + nsStaticAtom* const mName; + const SVGEnumMapping* const mMapping; + const uint16_t mDefaultValue; + }; + + using EnumAttributesInfo = AttributesInfo; + + struct NumberListInfo { + nsStaticAtom* const mName; + }; + + using NumberListAttributesInfo = + AttributesInfo; + + struct LengthListInfo { + nsStaticAtom* const mName; + const uint8_t mAxis; + /** + * Flag to indicate whether appending zeros to the end of the list would + * change the rendering of the SVG for the attribute in question. For x and + * y on the element this is true, but for dx and dy on this + * is false. This flag is fed down to SVGLengthListSMILType so it can + * determine if it can sensibly animate from-to lists of different lengths, + * which is desirable in the case of dx and dy. + */ + const bool mCouldZeroPadList; + }; + + using LengthListAttributesInfo = + AttributesInfo; + + struct StringInfo { + nsStaticAtom* const mName; + const int32_t mNamespaceID; + const bool mIsAnimatable; + }; + + using StringAttributesInfo = AttributesInfo; + + friend class DOMSVGStringList; + + struct StringListInfo { + nsStaticAtom* const mName; + }; + + using StringListAttributesInfo = + AttributesInfo; + + virtual LengthAttributesInfo GetLengthInfo(); + virtual NumberAttributesInfo GetNumberInfo(); + virtual NumberPairAttributesInfo GetNumberPairInfo(); + virtual IntegerAttributesInfo GetIntegerInfo(); + virtual IntegerPairAttributesInfo GetIntegerPairInfo(); + virtual BooleanAttributesInfo GetBooleanInfo(); + virtual EnumAttributesInfo GetEnumInfo(); + // We assume all orients, viewboxes and preserveAspectRatios are alike + // so we don't need to wrap the class + virtual SVGAnimatedOrient* GetAnimatedOrient(); + virtual SVGAnimatedPreserveAspectRatio* GetAnimatedPreserveAspectRatio(); + virtual SVGAnimatedViewBox* GetAnimatedViewBox(); + virtual NumberListAttributesInfo GetNumberListInfo(); + virtual LengthListAttributesInfo GetLengthListInfo(); + virtual StringAttributesInfo GetStringInfo(); + virtual StringListAttributesInfo GetStringListInfo(); + + static SVGEnumMapping sSVGUnitTypesMap[]; + + private: + void UnsetAttrInternal(int32_t aNameSpaceID, nsAtom* aName, bool aNotify); + + SVGAnimatedClass mClassAttribute; + UniquePtr mClassAnimAttr; + RefPtr mContentDeclarationBlock; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(SVGElement, MOZILLA_SVGELEMENT_IID) + +/** + * A macro to implement the NS_NewSVGXXXElement() functions. + */ +#define NS_IMPL_NS_NEW_SVG_ELEMENT(_elementName) \ + nsresult NS_NewSVG##_elementName##Element( \ + nsIContent** aResult, \ + already_AddRefed&& aNodeInfo) { \ + RefPtr nodeInfo(aNodeInfo); \ + auto* nim = nodeInfo->NodeInfoManager(); \ + RefPtr it = \ + new (nim) mozilla::dom::SVG##_elementName##Element(nodeInfo.forget()); \ + \ + nsresult rv = it->Init(); \ + \ + if (NS_FAILED(rv)) { \ + return rv; \ + } \ + \ + it.forget(aResult); \ + \ + return rv; \ + } + +#define NS_IMPL_NS_NEW_SVG_ELEMENT_CHECK_PARSER(_elementName) \ + nsresult NS_NewSVG##_elementName##Element( \ + nsIContent** aResult, \ + already_AddRefed&& aNodeInfo, \ + mozilla::dom::FromParser aFromParser) { \ + RefPtr nodeInfo(aNodeInfo); \ + auto* nim = nodeInfo->NodeInfoManager(); \ + RefPtr it = \ + new (nim) mozilla::dom::SVG##_elementName##Element(nodeInfo.forget(), \ + aFromParser); \ + \ + nsresult rv = it->Init(); \ + \ + if (NS_FAILED(rv)) { \ + return rv; \ + } \ + \ + it.forget(aResult); \ + \ + return rv; \ + } + +// No unlinking, we'd need to null out the value pointer (the object it +// points to is held by the element) and null-check it everywhere. +#define NS_SVG_VAL_IMPL_CYCLE_COLLECTION(_val, _element) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(_val) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_val) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_element) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_0(_val) + +#define NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(_val, _element) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(_val) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_val) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_val) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(_element) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \ + NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(_val) \ + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER \ + NS_IMPL_CYCLE_COLLECTION_TRACE_END + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGELEMENT_H_ diff --git a/dom/svg/SVGElementFactory.cpp b/dom/svg/SVGElementFactory.cpp new file mode 100644 index 0000000000..61cd2f65dd --- /dev/null +++ b/dom/svg/SVGElementFactory.cpp @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGElementFactory.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "mozilla/dom/NodeInfo.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/FromParser.h" +#include "mozilla/StaticPtr.h" +#include "nsTHashMap.h" +#include "nsHashKeys.h" + +using namespace mozilla; +using namespace mozilla::dom; + +// Hash table that maps nsAtom* SVG tags to a SVGContentCreatorFunction. +using TagAtomTable = + nsTHashMap, SVGContentCreatorFunction>; +StaticAutoPtr sTagAtomTable; + +#define SVG_TAG(_tag, _classname) \ + nsresult NS_NewSVG##_classname##Element( \ + nsIContent** aResult, \ + already_AddRefed&& aNodeInfo); \ + \ + nsresult NS_NewSVG##_classname##Element( \ + nsIContent** aResult, \ + already_AddRefed&& aNodeInfo, \ + FromParser aFromParser) { \ + return NS_NewSVG##_classname##Element(aResult, std::move(aNodeInfo)); \ + } + +#define SVG_FROM_PARSER_TAG(_tag, _classname) + +#include "SVGTagList.h" +#undef SVG_TAG +#undef SVG_FROM_PARSER_TAG + +nsresult NS_NewSVGElement(Element** aResult, + already_AddRefed&& aNodeInfo); + +enum SVGTag { +#define SVG_TAG(_tag, _classname) eSVGTag_##_tag, +#define SVG_FROM_PARSER_TAG(_tag, _classname) eSVGTag_##_tag, +#include "SVGTagList.h" +#undef SVG_TAG +#undef SVG_FROM_PARSER_TAG + eSVGTag_Count +}; + +void SVGElementFactory::Init() { + sTagAtomTable = new TagAtomTable(64); + +#define SVG_TAG(_tag, _classname) \ + sTagAtomTable->InsertOrUpdate( \ + nsGkAtoms::_tag, \ + SVGContentCreatorFunction(NS_NewSVG##_classname##Element)); +#define SVG_FROM_PARSER_TAG(_tag, _classname) \ + sTagAtomTable->InsertOrUpdate( \ + nsGkAtoms::_tag, \ + SVGContentCreatorFunction(NS_NewSVG##_classname##Element)); +#include "SVGTagList.h" +#undef SVG_TAG +#undef SVG_FROM_PARSER_TAG +} + +void SVGElementFactory::Shutdown() { sTagAtomTable = nullptr; } + +nsresult NS_NewSVGElement(Element** aResult, + already_AddRefed&& aNodeInfo, + FromParser aFromParser) { + NS_ASSERTION(sTagAtomTable, "no lookup table, needs SVGElementFactory::Init"); + + RefPtr ni = aNodeInfo; + nsAtom* name = ni->NameAtom(); + + NS_ASSERTION( + ni->NamespaceEquals(kNameSpaceID_SVG), + "Trying to create SVG elements that aren't in the SVG namespace"); + + SVGContentCreatorFunction cb = sTagAtomTable->Get(name); + if (cb) { + nsCOMPtr content; + nsresult rv = cb(getter_AddRefs(content), ni.forget(), aFromParser); + *aResult = content.forget().take()->AsElement(); + return rv; + } + + // if we don't know what to create, just create a standard svg element: + return NS_NewSVGElement(aResult, ni.forget()); +} + +nsresult NS_NewSVGUnknownElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo, + FromParser aFromParser) { + RefPtr ni = aNodeInfo; + nsCOMPtr element; + nsresult rv = NS_NewSVGElement(getter_AddRefs(element), ni.forget()); + element.forget(aResult); + return rv; +} diff --git a/dom/svg/SVGElementFactory.h b/dom/svg/SVGElementFactory.h new file mode 100644 index 0000000000..36a32f6904 --- /dev/null +++ b/dom/svg/SVGElementFactory.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGELEMENTFACTORY_H_ +#define DOM_SVG_SVGELEMENTFACTORY_H_ + +#include "nsError.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/dom/FromParser.h" +#include "mozilla/dom/NodeInfo.h" + +class nsAtom; +class nsIContent; + +namespace mozilla::dom { + +class SVGElementFactory { + public: + static void Init(); + static void Shutdown(); +}; + +using SVGContentCreatorFunction = nsresult (*)( + nsIContent** aResult, already_AddRefed&& aNodeInfo, + mozilla::dom::FromParser aFromParser); + +} // namespace mozilla::dom + +#define SVG_TAG(_tag, _classname) \ + nsresult NS_NewSVG##_classname##Element( \ + nsIContent** aResult, \ + already_AddRefed&& aNodeInfo, \ + mozilla::dom::FromParser aFromParser); + +#define SVG_FROM_PARSER_TAG(_tag, _classname) \ + nsresult NS_NewSVG##_classname##Element( \ + nsIContent** aResult, \ + already_AddRefed&& aNodeInfo, \ + mozilla::dom::FromParser aFromParser); +#include "mozilla/SVGTagList.h" +#undef SVG_TAG +#undef SVG_FROM_PARSER_TAG + +nsresult NS_NewSVGUnknownElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo, + mozilla::dom::FromParser aFromParser); + +#endif // DOM_SVG_SVGELEMENTFACTORY_H_ diff --git a/dom/svg/SVGEllipseElement.cpp b/dom/svg/SVGEllipseElement.cpp new file mode 100644 index 0000000000..b81d465915 --- /dev/null +++ b/dom/svg/SVGEllipseElement.cpp @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ComputedStyle.h" +#include "mozilla/dom/SVGEllipseElement.h" +#include "mozilla/dom/SVGEllipseElementBinding.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/RefPtr.h" +#include "SVGGeometryProperty.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Ellipse) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGEllipseElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGEllipseElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGEllipseElement::sLengthInfo[4] = { + {nsGkAtoms::cx, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::cy, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::rx, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::ry, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, +}; + +//---------------------------------------------------------------------- +// Implementation + +SVGEllipseElement::SVGEllipseElement( + already_AddRefed&& aNodeInfo) + : SVGEllipseElementBase(std::move(aNodeInfo)) {} + +bool SVGEllipseElement::IsAttributeMapped(const nsAtom* aAttribute) const { + return IsInLengthInfo(aAttribute, sLengthInfo) || + SVGEllipseElementBase::IsAttributeMapped(aAttribute); +} + +namespace SVGT = SVGGeometryProperty::Tags; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGEllipseElement) + +//---------------------------------------------------------------------- +// nsIDOMSVGEllipseElement methods + +already_AddRefed SVGEllipseElement::Cx() { + return mLengthAttributes[CX].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGEllipseElement::Cy() { + return mLengthAttributes[CY].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGEllipseElement::Rx() { + return mLengthAttributes[RX].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGEllipseElement::Ry() { + return mLengthAttributes[RY].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +bool SVGEllipseElement::HasValidDimensions() const { + float rx, ry; + + if (SVGGeometryProperty::ResolveAll(this, &rx, &ry)) { + return rx > 0 && ry > 0; + } + // This function might be called for an element in display:none subtree + // (e.g. SMIL animateMotion), we fall back to use SVG attributes. + bool hasRx = mLengthAttributes[RX].IsExplicitlySet(); + bool hasRy = mLengthAttributes[RY].IsExplicitlySet(); + if ((hasRx && mLengthAttributes[RX].GetAnimValInSpecifiedUnits() <= 0) || + (hasRy && mLengthAttributes[RY].GetAnimValInSpecifiedUnits() <= 0)) { + return false; + } + return hasRx || hasRy; +} + +SVGElement::LengthAttributesInfo SVGEllipseElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +bool SVGEllipseElement::GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, const Matrix* aToNonScalingStrokeSpace) { + float x, y, rx, ry; + + DebugOnly ok = + SVGGeometryProperty::ResolveAll( + this, &x, &y, &rx, &ry); + MOZ_ASSERT(ok, "SVGGeometryProperty::ResolveAll failed"); + + if (rx <= 0.f || ry <= 0.f) { + // Rendering of the element is disabled + *aBounds = Rect(aToBoundsSpace.TransformPoint(Point(x, y)), Size()); + return true; + } + + if (aToBoundsSpace.IsRectilinear()) { + // Optimize the case where we can treat the ellipse as a rectangle and + // still get tight bounds. + if (aStrokeOptions.mLineWidth > 0.f) { + if (aToNonScalingStrokeSpace) { + if (aToNonScalingStrokeSpace->IsRectilinear()) { + MOZ_ASSERT(!aToNonScalingStrokeSpace->IsSingular()); + Rect userBounds(x - rx, y - ry, 2 * rx, 2 * ry); + SVGContentUtils::RectilinearGetStrokeBounds( + userBounds, aToBoundsSpace, *aToNonScalingStrokeSpace, + aStrokeOptions.mLineWidth, aBounds); + return true; + } + return false; + } + rx += aStrokeOptions.mLineWidth / 2.f; + ry += aStrokeOptions.mLineWidth / 2.f; + } + Rect rect(x - rx, y - ry, 2 * rx, 2 * ry); + *aBounds = aToBoundsSpace.TransformBounds(rect); + return true; + } + + return false; +} + +already_AddRefed SVGEllipseElement::BuildPath(PathBuilder* aBuilder) { + float x, y, rx, ry; + + if (!SVGGeometryProperty::ResolveAll( + this, &x, &y, &rx, &ry)) { + // This function might be called for element in display:none subtree + // (e.g. getTotalLength), we fall back to use SVG attributes. + GetAnimatedLengthValues(&x, &y, &rx, &ry, nullptr); + } + + if (rx <= 0.0f || ry <= 0.0f) { + return nullptr; + } + + EllipseToBezier(aBuilder, Point(x, y), Size(rx, ry)); + + return aBuilder->Finish(); +} + +bool SVGEllipseElement::IsLengthChangedViaCSS(const ComputedStyle& aNewStyle, + const ComputedStyle& aOldStyle) { + const auto& newSVGReset = *aNewStyle.StyleSVGReset(); + const auto& oldSVGReset = *aOldStyle.StyleSVGReset(); + return newSVGReset.mCx != oldSVGReset.mCx || + newSVGReset.mCy != oldSVGReset.mCy || + newSVGReset.mRx != oldSVGReset.mRx || + newSVGReset.mRy != oldSVGReset.mRy; +} + +nsCSSPropertyID SVGEllipseElement::GetCSSPropertyIdForAttrEnum( + uint8_t aAttrEnum) { + switch (aAttrEnum) { + case CX: + return eCSSProperty_cx; + case CY: + return eCSSProperty_cy; + case RX: + return eCSSProperty_rx; + case RY: + return eCSSProperty_ry; + default: + MOZ_ASSERT_UNREACHABLE("Unknown attr enum"); + return eCSSProperty_UNKNOWN; + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGEllipseElement.h b/dom/svg/SVGEllipseElement.h new file mode 100644 index 0000000000..c4fa308556 --- /dev/null +++ b/dom/svg/SVGEllipseElement.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGELLIPSEELEMENT_H_ +#define DOM_SVG_SVGELLIPSEELEMENT_H_ + +#include "nsCSSPropertyID.h" +#include "SVGAnimatedLength.h" +#include "SVGGeometryElement.h" + +nsresult NS_NewSVGEllipseElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla { +class ComputedStyle; + +namespace dom { + +using SVGEllipseElementBase = SVGGeometryElement; + +class SVGEllipseElement final : public SVGEllipseElementBase { + protected: + explicit SVGEllipseElement( + already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + friend nsresult(::NS_NewSVGEllipseElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + public: + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + // SVGGeometryElement methods: + virtual bool GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; + already_AddRefed BuildPath(PathBuilder* aBuilder) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + static bool IsLengthChangedViaCSS(const ComputedStyle& aNewStyle, + const ComputedStyle& aOldStyle); + static nsCSSPropertyID GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum); + + // WebIDL + already_AddRefed Cx(); + already_AddRefed Cy(); + already_AddRefed Rx(); + already_AddRefed Ry(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + + enum { CX, CY, RX, RY }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGELLIPSEELEMENT_H_ diff --git a/dom/svg/SVGFEBlendElement.cpp b/dom/svg/SVGFEBlendElement.cpp new file mode 100644 index 0000000000..b5ba3ab198 --- /dev/null +++ b/dom/svg/SVGFEBlendElement.cpp @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEBlendElement.h" +#include "mozilla/dom/SVGFEBlendElementBinding.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEBlend) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEBlendElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFEBlendElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGEnumMapping SVGFEBlendElement::sModeMap[] = { + {nsGkAtoms::normal, SVG_FEBLEND_MODE_NORMAL}, + {nsGkAtoms::multiply, SVG_FEBLEND_MODE_MULTIPLY}, + {nsGkAtoms::screen, SVG_FEBLEND_MODE_SCREEN}, + {nsGkAtoms::darken, SVG_FEBLEND_MODE_DARKEN}, + {nsGkAtoms::lighten, SVG_FEBLEND_MODE_LIGHTEN}, + {nsGkAtoms::overlay, SVG_FEBLEND_MODE_OVERLAY}, + {nsGkAtoms::colorDodge, SVG_FEBLEND_MODE_COLOR_DODGE}, + {nsGkAtoms::colorBurn, SVG_FEBLEND_MODE_COLOR_BURN}, + {nsGkAtoms::hardLight, SVG_FEBLEND_MODE_HARD_LIGHT}, + {nsGkAtoms::softLight, SVG_FEBLEND_MODE_SOFT_LIGHT}, + {nsGkAtoms::difference, SVG_FEBLEND_MODE_DIFFERENCE}, + {nsGkAtoms::exclusion, SVG_FEBLEND_MODE_EXCLUSION}, + {nsGkAtoms::hue, SVG_FEBLEND_MODE_HUE}, + {nsGkAtoms::saturation, SVG_FEBLEND_MODE_SATURATION}, + {nsGkAtoms::color, SVG_FEBLEND_MODE_COLOR}, + {nsGkAtoms::luminosity, SVG_FEBLEND_MODE_LUMINOSITY}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGFEBlendElement::sEnumInfo[1] = { + {nsGkAtoms::mode, sModeMap, SVG_FEBLEND_MODE_NORMAL}}; + +SVGElement::StringInfo SVGFEBlendElement::sStringInfo[3] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}, + {nsGkAtoms::in2, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEBlendElement) + +//---------------------------------------------------------------------- +// nsIDOMSVGFEBlendElement methods + +already_AddRefed SVGFEBlendElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed SVGFEBlendElement::In2() { + return mStringAttributes[IN2].ToDOMAnimatedString(this); +} + +already_AddRefed SVGFEBlendElement::Mode() { + return mEnumAttributes[MODE].ToDOMAnimatedEnum(this); +} + +FilterPrimitiveDescription SVGFEBlendElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + uint32_t mode = mEnumAttributes[MODE].GetAnimValue(); + BlendAttributes attributes; + attributes.mBlendMode = mode; + return FilterPrimitiveDescription(AsVariant(std::move(attributes))); +} + +bool SVGFEBlendElement::AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const { + return SVGFEBlendElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || aAttribute == nsGkAtoms::in2 || + aAttribute == nsGkAtoms::mode)); +} + +void SVGFEBlendElement::GetSourceImageNames(nsTArray& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN2], this)); +} + +nsresult SVGFEBlendElement::BindToTree(BindContext& aCtx, nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feBlend); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::EnumAttributesInfo SVGFEBlendElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGFEBlendElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEBlendElement.h b/dom/svg/SVGFEBlendElement.h new file mode 100644 index 0000000000..79a1703552 --- /dev/null +++ b/dom/svg/SVGFEBlendElement.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEBLENDELEMENT_H_ +#define DOM_SVG_SVGFEBLENDELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEBlendElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); +namespace mozilla::dom { + +using SVGFEBlendElementBase = SVGFE; + +class SVGFEBlendElement final : public SVGFEBlendElementBase { + friend nsresult(::NS_NewSVGFEBlendElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEBlendElement( + already_AddRefed&& aNodeInfo) + : SVGFEBlendElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + + // WebIDL + already_AddRefed In1(); + already_AddRefed In2(); + already_AddRefed Mode(); + + protected: + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { MODE }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sModeMap[]; + static EnumInfo sEnumInfo[1]; + + enum { RESULT, IN1, IN2 }; + SVGAnimatedString mStringAttributes[3]; + static StringInfo sStringInfo[3]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEBLENDELEMENT_H_ diff --git a/dom/svg/SVGFEColorMatrixElement.cpp b/dom/svg/SVGFEColorMatrixElement.cpp new file mode 100644 index 0000000000..9295a8bdbb --- /dev/null +++ b/dom/svg/SVGFEColorMatrixElement.cpp @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEColorMatrixElement.h" + +#include "DOMSVGAnimatedNumberList.h" +#include "mozilla/dom/SVGFEColorMatrixElementBinding.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +#define NUM_ENTRIES_IN_4x5_MATRIX 20 + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEColorMatrix) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEColorMatrixElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFEColorMatrixElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGEnumMapping SVGFEColorMatrixElement::sTypeMap[] = { + {nsGkAtoms::matrix, SVG_FECOLORMATRIX_TYPE_MATRIX}, + {nsGkAtoms::saturate, SVG_FECOLORMATRIX_TYPE_SATURATE}, + {nsGkAtoms::hueRotate, SVG_FECOLORMATRIX_TYPE_HUE_ROTATE}, + {nsGkAtoms::luminanceToAlpha, SVG_FECOLORMATRIX_TYPE_LUMINANCE_TO_ALPHA}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGFEColorMatrixElement::sEnumInfo[1] = { + {nsGkAtoms::type, sTypeMap, SVG_FECOLORMATRIX_TYPE_MATRIX}}; + +SVGElement::StringInfo SVGFEColorMatrixElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +SVGElement::NumberListInfo SVGFEColorMatrixElement::sNumberListInfo[1] = { + {nsGkAtoms::values}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEColorMatrixElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGFEColorMatrixElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed SVGFEColorMatrixElement::Type() { + return mEnumAttributes[TYPE].ToDOMAnimatedEnum(this); +} + +already_AddRefed SVGFEColorMatrixElement::Values() { + return DOMSVGAnimatedNumberList::GetDOMWrapper(&mNumberListAttributes[VALUES], + this, VALUES); +} + +void SVGFEColorMatrixElement::GetSourceImageNames( + nsTArray& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +FilterPrimitiveDescription SVGFEColorMatrixElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + uint32_t type = mEnumAttributes[TYPE].GetAnimValue(); + const SVGNumberList& values = mNumberListAttributes[VALUES].GetAnimValue(); + + ColorMatrixAttributes atts; + if (!mNumberListAttributes[VALUES].IsExplicitlySet() && + (type == SVG_FECOLORMATRIX_TYPE_MATRIX || + type == SVG_FECOLORMATRIX_TYPE_SATURATE || + type == SVG_FECOLORMATRIX_TYPE_HUE_ROTATE)) { + atts.mType = (uint32_t)SVG_FECOLORMATRIX_TYPE_MATRIX; + static const float identityMatrix[] = { + // clang-format off + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + // clang-format on + }; + atts.mValues.AppendElements(identityMatrix, 20); + } else { + atts.mType = type; + if (values.Length()) { + atts.mValues.AppendElements(&values[0], values.Length()); + } + } + + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEColorMatrixElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFEColorMatrixElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || aAttribute == nsGkAtoms::type || + aAttribute == nsGkAtoms::values)); +} + +nsresult SVGFEColorMatrixElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feColorMatrix); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::EnumAttributesInfo SVGFEColorMatrixElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGFEColorMatrixElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +SVGElement::NumberListAttributesInfo +SVGFEColorMatrixElement::GetNumberListInfo() { + return NumberListAttributesInfo(mNumberListAttributes, sNumberListInfo, + ArrayLength(sNumberListInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEColorMatrixElement.h b/dom/svg/SVGFEColorMatrixElement.h new file mode 100644 index 0000000000..b1b679a738 --- /dev/null +++ b/dom/svg/SVGFEColorMatrixElement.h @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFECOLORMATRIXELEMENT_H_ +#define DOM_SVG_SVGFECOLORMATRIXELEMENT_H_ + +#include "SVGAnimatedNumberList.h" +#include "SVGAnimatedEnumeration.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEColorMatrixElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +class DOMSVGAnimatedNumberList; + +using SVGFEColorMatrixElementBase = SVGFE; + +class SVGFEColorMatrixElement final : public SVGFEColorMatrixElementBase { + friend nsresult(::NS_NewSVGFEColorMatrixElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEColorMatrixElement( + already_AddRefed&& aNodeInfo) + : SVGFEColorMatrixElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + // WebIDL + already_AddRefed In1(); + already_AddRefed Type(); + already_AddRefed Values(); + + protected: + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + NumberListAttributesInfo GetNumberListInfo() override; + + enum { TYPE }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sTypeMap[]; + static EnumInfo sEnumInfo[1]; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; + + enum { VALUES }; + SVGAnimatedNumberList mNumberListAttributes[1]; + static NumberListInfo sNumberListInfo[1]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFECOLORMATRIXELEMENT_H_ diff --git a/dom/svg/SVGFEComponentTransferElement.cpp b/dom/svg/SVGFEComponentTransferElement.cpp new file mode 100644 index 0000000000..d313c9ad15 --- /dev/null +++ b/dom/svg/SVGFEComponentTransferElement.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEComponentTransferElement.h" + +#include "mozilla/dom/SVGComponentTransferFunctionElement.h" +#include "mozilla/dom/SVGFEComponentTransferElementBinding.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEComponentTransfer) + +using namespace mozilla::gfx; +; + +namespace mozilla::dom { + +JSObject* SVGFEComponentTransferElement::WrapNode( + JSContext* aCx, JS::Handle aGivenProto) { + return SVGFEComponentTransferElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGFEComponentTransferElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEComponentTransferElement) + +already_AddRefed SVGFEComponentTransferElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::StringAttributesInfo +SVGFEComponentTransferElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +//-------------------------------------------- + +FilterPrimitiveDescription +SVGFEComponentTransferElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + RefPtr childForChannel[4]; + + for (nsIContent* childContent = nsINode::GetFirstChild(); childContent; + childContent = childContent->GetNextSibling()) { + RefPtr child; + CallQueryInterface( + childContent, + (SVGComponentTransferFunctionElement**)getter_AddRefs(child)); + if (child) { + childForChannel[child->GetChannel()] = child; + } + } + + ComponentTransferAttributes atts; + for (int32_t i = 0; i < 4; i++) { + if (childForChannel[i]) { + childForChannel[i]->ComputeAttributes(i, atts); + } else { + atts.mTypes[i] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY; + } + } + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEComponentTransferElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFEComponentTransferElementBase::AttributeAffectsRendering( + aNameSpaceID, aAttribute) || + (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::in); +} + +void SVGFEComponentTransferElement::GetSourceImageNames( + nsTArray& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +nsresult SVGFEComponentTransferElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feComponentTransfer); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEComponentTransferElement.h b/dom/svg/SVGFEComponentTransferElement.h new file mode 100644 index 0000000000..49573ed32b --- /dev/null +++ b/dom/svg/SVGFEComponentTransferElement.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFECOMPONENTTRANSFERELEMENT_H_ +#define DOM_SVG_SVGFECOMPONENTTRANSFERELEMENT_H_ + +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEComponentTransferElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEComponentTransferElementBase = SVGFE; + +class SVGFEComponentTransferElement final + : public SVGFEComponentTransferElementBase { + friend nsresult(::NS_NewSVGFEComponentTransferElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEComponentTransferElement( + already_AddRefed&& aNodeInfo) + : SVGFEComponentTransferElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray& aSources) override; + + // nsIContent + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + + // WebIDL + already_AddRefed In1(); + + protected: + StringAttributesInfo GetStringInfo() override; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFECOMPONENTTRANSFERELEMENT_H_ diff --git a/dom/svg/SVGFECompositeElement.cpp b/dom/svg/SVGFECompositeElement.cpp new file mode 100644 index 0000000000..04cff7eb23 --- /dev/null +++ b/dom/svg/SVGFECompositeElement.cpp @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFECompositeElement.h" +#include "mozilla/dom/SVGFECompositeElementBinding.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEComposite) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFECompositeElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFECompositeElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFECompositeElement::sNumberInfo[4] = { + {nsGkAtoms::k1, 0, false}, + {nsGkAtoms::k2, 0, false}, + {nsGkAtoms::k3, 0, false}, + {nsGkAtoms::k4, 0, false}}; + +SVGEnumMapping SVGFECompositeElement::sOperatorMap[] = { + {nsGkAtoms::over, SVG_FECOMPOSITE_OPERATOR_OVER}, + {nsGkAtoms::in, SVG_FECOMPOSITE_OPERATOR_IN}, + {nsGkAtoms::out, SVG_FECOMPOSITE_OPERATOR_OUT}, + {nsGkAtoms::atop, SVG_FECOMPOSITE_OPERATOR_ATOP}, + {nsGkAtoms::xor_, SVG_FECOMPOSITE_OPERATOR_XOR}, + {nsGkAtoms::arithmetic, SVG_FECOMPOSITE_OPERATOR_ARITHMETIC}, + {nsGkAtoms::lighter, SVG_FECOMPOSITE_OPERATOR_LIGHTER}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGFECompositeElement::sEnumInfo[1] = { + {nsGkAtoms::_operator, sOperatorMap, SVG_FECOMPOSITE_OPERATOR_OVER}}; + +SVGElement::StringInfo SVGFECompositeElement::sStringInfo[3] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}, + {nsGkAtoms::in2, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFECompositeElement) + +already_AddRefed SVGFECompositeElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed SVGFECompositeElement::In2() { + return mStringAttributes[IN2].ToDOMAnimatedString(this); +} + +already_AddRefed SVGFECompositeElement::Operator() { + return mEnumAttributes[OPERATOR].ToDOMAnimatedEnum(this); +} + +already_AddRefed SVGFECompositeElement::K1() { + return mNumberAttributes[ATTR_K1].ToDOMAnimatedNumber(this); +} + +already_AddRefed SVGFECompositeElement::K2() { + return mNumberAttributes[ATTR_K2].ToDOMAnimatedNumber(this); +} + +already_AddRefed SVGFECompositeElement::K3() { + return mNumberAttributes[ATTR_K3].ToDOMAnimatedNumber(this); +} + +already_AddRefed SVGFECompositeElement::K4() { + return mNumberAttributes[ATTR_K4].ToDOMAnimatedNumber(this); +} + +void SVGFECompositeElement::SetK(float k1, float k2, float k3, float k4) { + mNumberAttributes[ATTR_K1].SetBaseValue(k1, this); + mNumberAttributes[ATTR_K2].SetBaseValue(k2, this); + mNumberAttributes[ATTR_K3].SetBaseValue(k3, this); + mNumberAttributes[ATTR_K4].SetBaseValue(k4, this); +} + +FilterPrimitiveDescription SVGFECompositeElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + CompositeAttributes atts; + uint32_t op = mEnumAttributes[OPERATOR].GetAnimValue(); + atts.mOperator = op; + + if (op == SVG_FECOMPOSITE_OPERATOR_ARITHMETIC) { + float k[4]; + GetAnimatedNumberValues(k, k + 1, k + 2, k + 3, nullptr); + atts.mCoefficients.AppendElements(k, 4); + } + + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFECompositeElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFECompositeElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || aAttribute == nsGkAtoms::in2 || + aAttribute == nsGkAtoms::k1 || aAttribute == nsGkAtoms::k2 || + aAttribute == nsGkAtoms::k3 || aAttribute == nsGkAtoms::k4 || + aAttribute == nsGkAtoms::_operator)); +} + +void SVGFECompositeElement::GetSourceImageNames( + nsTArray& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN2], this)); +} + +nsresult SVGFECompositeElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feComposite); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFECompositeElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +SVGElement::EnumAttributesInfo SVGFECompositeElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGFECompositeElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFECompositeElement.h b/dom/svg/SVGFECompositeElement.h new file mode 100644 index 0000000000..6f97dafdbd --- /dev/null +++ b/dom/svg/SVGFECompositeElement.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFECOMPOSITEELEMENT_H_ +#define DOM_SVG_SVGFECOMPOSITEELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedNumber.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFECompositeElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFECompositeElementBase = SVGFE; + +class SVGFECompositeElement final : public SVGFECompositeElementBase { + friend nsresult(::NS_NewSVGFECompositeElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFECompositeElement( + already_AddRefed&& aNodeInfo) + : SVGFECompositeElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + // WebIDL + already_AddRefed In1(); + already_AddRefed In2(); + already_AddRefed Operator(); + already_AddRefed K1(); + already_AddRefed K2(); + already_AddRefed K3(); + already_AddRefed K4(); + void SetK(float k1, float k2, float k3, float k4); + + protected: + NumberAttributesInfo GetNumberInfo() override; + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { ATTR_K1, ATTR_K2, ATTR_K3, ATTR_K4 }; + SVGAnimatedNumber mNumberAttributes[4]; + static NumberInfo sNumberInfo[4]; + + enum { OPERATOR }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sOperatorMap[]; + static EnumInfo sEnumInfo[1]; + + enum { RESULT, IN1, IN2 }; + SVGAnimatedString mStringAttributes[3]; + static StringInfo sStringInfo[3]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFECOMPOSITEELEMENT_H_ diff --git a/dom/svg/SVGFEConvolveMatrixElement.cpp b/dom/svg/SVGFEConvolveMatrixElement.cpp new file mode 100644 index 0000000000..4961419e7f --- /dev/null +++ b/dom/svg/SVGFEConvolveMatrixElement.cpp @@ -0,0 +1,278 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEConvolveMatrixElement.h" +#include "mozilla/dom/SVGFEConvolveMatrixElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "DOMSVGAnimatedNumberList.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEConvolveMatrix) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEConvolveMatrixElement::WrapNode( + JSContext* aCx, JS::Handle aGivenProto) { + return SVGFEConvolveMatrixElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFEConvolveMatrixElement::sNumberInfo[2] = { + {nsGkAtoms::divisor, 1, false}, {nsGkAtoms::bias, 0, false}}; + +SVGElement::NumberPairInfo SVGFEConvolveMatrixElement::sNumberPairInfo[1] = { + {nsGkAtoms::kernelUnitLength, 0, 0}}; + +SVGElement::IntegerInfo SVGFEConvolveMatrixElement::sIntegerInfo[2] = { + {nsGkAtoms::targetX, 0}, {nsGkAtoms::targetY, 0}}; + +SVGElement::IntegerPairInfo SVGFEConvolveMatrixElement::sIntegerPairInfo[1] = { + {nsGkAtoms::order, 3, 3}}; + +SVGElement::BooleanInfo SVGFEConvolveMatrixElement::sBooleanInfo[1] = { + {nsGkAtoms::preserveAlpha, false}}; + +SVGEnumMapping SVGFEConvolveMatrixElement::sEdgeModeMap[] = { + {nsGkAtoms::duplicate, SVG_EDGEMODE_DUPLICATE}, + {nsGkAtoms::wrap, SVG_EDGEMODE_WRAP}, + {nsGkAtoms::none, SVG_EDGEMODE_NONE}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGFEConvolveMatrixElement::sEnumInfo[1] = { + {nsGkAtoms::edgeMode, sEdgeModeMap, SVG_EDGEMODE_DUPLICATE}}; + +SVGElement::StringInfo SVGFEConvolveMatrixElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +SVGElement::NumberListInfo SVGFEConvolveMatrixElement::sNumberListInfo[1] = { + {nsGkAtoms::kernelMatrix}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEConvolveMatrixElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGFEConvolveMatrixElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed SVGFEConvolveMatrixElement::OrderX() { + return mIntegerPairAttributes[ORDER].ToDOMAnimatedInteger( + SVGAnimatedIntegerPair::eFirst, this); +} + +already_AddRefed SVGFEConvolveMatrixElement::OrderY() { + return mIntegerPairAttributes[ORDER].ToDOMAnimatedInteger( + SVGAnimatedIntegerPair::eSecond, this); +} + +already_AddRefed +SVGFEConvolveMatrixElement::KernelMatrix() { + return DOMSVGAnimatedNumberList::GetDOMWrapper( + &mNumberListAttributes[KERNELMATRIX], this, KERNELMATRIX); +} + +already_AddRefed SVGFEConvolveMatrixElement::TargetX() { + return mIntegerAttributes[TARGET_X].ToDOMAnimatedInteger(this); +} + +already_AddRefed SVGFEConvolveMatrixElement::TargetY() { + return mIntegerAttributes[TARGET_Y].ToDOMAnimatedInteger(this); +} + +already_AddRefed +SVGFEConvolveMatrixElement::EdgeMode() { + return mEnumAttributes[EDGEMODE].ToDOMAnimatedEnum(this); +} + +already_AddRefed +SVGFEConvolveMatrixElement::PreserveAlpha() { + return mBooleanAttributes[PRESERVEALPHA].ToDOMAnimatedBoolean(this); +} + +already_AddRefed SVGFEConvolveMatrixElement::Divisor() { + return mNumberAttributes[DIVISOR].ToDOMAnimatedNumber(this); +} + +already_AddRefed SVGFEConvolveMatrixElement::Bias() { + return mNumberAttributes[BIAS].ToDOMAnimatedNumber(this); +} + +already_AddRefed +SVGFEConvolveMatrixElement::KernelUnitLengthX() { + return mNumberPairAttributes[KERNEL_UNIT_LENGTH].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eFirst, this); +} + +already_AddRefed +SVGFEConvolveMatrixElement::KernelUnitLengthY() { + return mNumberPairAttributes[KERNEL_UNIT_LENGTH].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eSecond, this); +} + +void SVGFEConvolveMatrixElement::GetSourceImageNames( + nsTArray& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +FilterPrimitiveDescription SVGFEConvolveMatrixElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + FilterPrimitiveDescription failureDescription; + + const SVGNumberList& kernelMatrix = + mNumberListAttributes[KERNELMATRIX].GetAnimValue(); + uint32_t kmLength = kernelMatrix.Length(); + + int32_t orderX = mIntegerPairAttributes[ORDER].GetAnimValue( + SVGAnimatedIntegerPair::eFirst); + int32_t orderY = mIntegerPairAttributes[ORDER].GetAnimValue( + SVGAnimatedIntegerPair::eSecond); + + if (orderX <= 0 || orderY <= 0 || + static_cast(orderX * orderY) != kmLength) { + return failureDescription; + } + + int32_t targetX, targetY; + GetAnimatedIntegerValues(&targetX, &targetY, nullptr); + + if (mIntegerAttributes[TARGET_X].IsExplicitlySet()) { + if (targetX < 0 || targetX >= orderX) return failureDescription; + } else { + targetX = orderX / 2; + } + if (mIntegerAttributes[TARGET_Y].IsExplicitlySet()) { + if (targetY < 0 || targetY >= orderY) return failureDescription; + } else { + targetY = orderY / 2; + } + + if (orderX > NS_SVG_OFFSCREEN_MAX_DIMENSION || + orderY > NS_SVG_OFFSCREEN_MAX_DIMENSION) + return failureDescription; + UniquePtr kernel = MakeUniqueFallible(orderX * orderY); + if (!kernel) return failureDescription; + for (uint32_t i = 0; i < kmLength; i++) { + kernel[kmLength - 1 - i] = kernelMatrix[i]; + } + + float divisor; + if (mNumberAttributes[DIVISOR].IsExplicitlySet()) { + divisor = mNumberAttributes[DIVISOR].GetAnimValue(); + if (divisor == 0) return failureDescription; + } else { + divisor = kernel[0]; + for (uint32_t i = 1; i < kmLength; i++) divisor += kernel[i]; + if (divisor == 0) divisor = 1; + } + + uint32_t edgeMode = mEnumAttributes[EDGEMODE].GetAnimValue(); + bool preserveAlpha = mBooleanAttributes[PRESERVEALPHA].GetAnimValue(); + float bias = mNumberAttributes[BIAS].GetAnimValue(); + + Size kernelUnitLength = GetKernelUnitLength( + aInstance, &mNumberPairAttributes[KERNEL_UNIT_LENGTH]); + + if (kernelUnitLength.width <= 0 || kernelUnitLength.height <= 0) { + // According to spec, A negative or zero value is an error. See link below + // for details. + // https://www.w3.org/TR/SVG/filters.html#feConvolveMatrixElementKernelUnitLengthAttribute + return failureDescription; + } + + ConvolveMatrixAttributes atts; + atts.mKernelSize = IntSize(orderX, orderY); + atts.mKernelMatrix.AppendElements(&kernelMatrix[0], kmLength); + atts.mDivisor = divisor; + atts.mBias = bias; + atts.mTarget = IntPoint(targetX, targetY); + atts.mEdgeMode = edgeMode; + atts.mKernelUnitLength = kernelUnitLength; + atts.mPreserveAlpha = preserveAlpha; + + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEConvolveMatrixElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFEConvolveMatrixElementBase::AttributeAffectsRendering( + aNameSpaceID, aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || aAttribute == nsGkAtoms::divisor || + aAttribute == nsGkAtoms::bias || + aAttribute == nsGkAtoms::kernelUnitLength || + aAttribute == nsGkAtoms::targetX || + aAttribute == nsGkAtoms::targetY || aAttribute == nsGkAtoms::order || + aAttribute == nsGkAtoms::preserveAlpha || + aAttribute == nsGkAtoms::edgeMode || + aAttribute == nsGkAtoms::kernelMatrix)); +} + +nsresult SVGFEConvolveMatrixElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feConvolveMatrix); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFEConvolveMatrixElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +SVGElement::NumberPairAttributesInfo +SVGFEConvolveMatrixElement::GetNumberPairInfo() { + return NumberPairAttributesInfo(mNumberPairAttributes, sNumberPairInfo, + ArrayLength(sNumberPairInfo)); +} + +SVGElement::IntegerAttributesInfo SVGFEConvolveMatrixElement::GetIntegerInfo() { + return IntegerAttributesInfo(mIntegerAttributes, sIntegerInfo, + ArrayLength(sIntegerInfo)); +} + +SVGElement::IntegerPairAttributesInfo +SVGFEConvolveMatrixElement::GetIntegerPairInfo() { + return IntegerPairAttributesInfo(mIntegerPairAttributes, sIntegerPairInfo, + ArrayLength(sIntegerPairInfo)); +} + +SVGElement::BooleanAttributesInfo SVGFEConvolveMatrixElement::GetBooleanInfo() { + return BooleanAttributesInfo(mBooleanAttributes, sBooleanInfo, + ArrayLength(sBooleanInfo)); +} + +SVGElement::EnumAttributesInfo SVGFEConvolveMatrixElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGFEConvolveMatrixElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +SVGElement::NumberListAttributesInfo +SVGFEConvolveMatrixElement::GetNumberListInfo() { + return NumberListAttributesInfo(mNumberListAttributes, sNumberListInfo, + ArrayLength(sNumberListInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEConvolveMatrixElement.h b/dom/svg/SVGFEConvolveMatrixElement.h new file mode 100644 index 0000000000..3b06eeae57 --- /dev/null +++ b/dom/svg/SVGFEConvolveMatrixElement.h @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFECONVOLVEMATRIXELEMENT_H_ +#define DOM_SVG_SVGFECONVOLVEMATRIXELEMENT_H_ + +#include "SVGAnimatedBoolean.h" +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedInteger.h" +#include "SVGAnimatedIntegerPair.h" +#include "SVGAnimatedNumber.h" +#include "SVGAnimatedNumberList.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEConvolveMatrixElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { +class DOMSVGAnimatedNumberList; +class DOMSVGAnimatedBoolean; + +using SVGFEConvolveMatrixElementBase = SVGFE; + +class SVGFEConvolveMatrixElement final : public SVGFEConvolveMatrixElementBase { + friend nsresult(::NS_NewSVGFEConvolveMatrixElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEConvolveMatrixElement( + already_AddRefed&& aNodeInfo) + : SVGFEConvolveMatrixElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + // WebIDL + already_AddRefed In1(); + already_AddRefed OrderX(); + already_AddRefed OrderY(); + already_AddRefed KernelMatrix(); + already_AddRefed TargetX(); + already_AddRefed TargetY(); + already_AddRefed EdgeMode(); + already_AddRefed PreserveAlpha(); + already_AddRefed Divisor(); + already_AddRefed Bias(); + already_AddRefed KernelUnitLengthX(); + already_AddRefed KernelUnitLengthY(); + + protected: + NumberAttributesInfo GetNumberInfo() override; + NumberPairAttributesInfo GetNumberPairInfo() override; + IntegerAttributesInfo GetIntegerInfo() override; + IntegerPairAttributesInfo GetIntegerPairInfo() override; + BooleanAttributesInfo GetBooleanInfo() override; + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + NumberListAttributesInfo GetNumberListInfo() override; + + enum { DIVISOR, BIAS }; + SVGAnimatedNumber mNumberAttributes[2]; + static NumberInfo sNumberInfo[2]; + + enum { KERNEL_UNIT_LENGTH }; + SVGAnimatedNumberPair mNumberPairAttributes[1]; + static NumberPairInfo sNumberPairInfo[1]; + + enum { TARGET_X, TARGET_Y }; + SVGAnimatedInteger mIntegerAttributes[2]; + static IntegerInfo sIntegerInfo[2]; + + enum { ORDER }; + SVGAnimatedIntegerPair mIntegerPairAttributes[1]; + static IntegerPairInfo sIntegerPairInfo[1]; + + enum { PRESERVEALPHA }; + SVGAnimatedBoolean mBooleanAttributes[1]; + static BooleanInfo sBooleanInfo[1]; + + enum { EDGEMODE }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sEdgeModeMap[]; + static EnumInfo sEnumInfo[1]; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; + + enum { KERNELMATRIX }; + SVGAnimatedNumberList mNumberListAttributes[1]; + static NumberListInfo sNumberListInfo[1]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFECONVOLVEMATRIXELEMENT_H_ diff --git a/dom/svg/SVGFEDiffuseLightingElement.cpp b/dom/svg/SVGFEDiffuseLightingElement.cpp new file mode 100644 index 0000000000..f80daa5fd1 --- /dev/null +++ b/dom/svg/SVGFEDiffuseLightingElement.cpp @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEDiffuseLightingElement.h" +#include "mozilla/dom/SVGFEDiffuseLightingElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEDiffuseLighting) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEDiffuseLightingElement::WrapNode( + JSContext* aCx, JS::Handle aGivenProto) { + return SVGFEDiffuseLightingElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEDiffuseLightingElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGFEDiffuseLightingElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed +SVGFEDiffuseLightingElement::SurfaceScale() { + return mNumberAttributes[SURFACE_SCALE].ToDOMAnimatedNumber(this); +} + +already_AddRefed +SVGFEDiffuseLightingElement::DiffuseConstant() { + return mNumberAttributes[DIFFUSE_CONSTANT].ToDOMAnimatedNumber(this); +} + +already_AddRefed +SVGFEDiffuseLightingElement::KernelUnitLengthX() { + return mNumberPairAttributes[KERNEL_UNIT_LENGTH].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eFirst, this); +} + +already_AddRefed +SVGFEDiffuseLightingElement::KernelUnitLengthY() { + return mNumberPairAttributes[KERNEL_UNIT_LENGTH].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eSecond, this); +} + +FilterPrimitiveDescription SVGFEDiffuseLightingElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + float diffuseConstant = mNumberAttributes[DIFFUSE_CONSTANT].GetAnimValue(); + + DiffuseLightingAttributes atts; + atts.mLightingConstant = diffuseConstant; + if (!AddLightingAttributes(&atts, aInstance)) { + return FilterPrimitiveDescription(); + } + + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEDiffuseLightingElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFEDiffuseLightingElementBase::AttributeAffectsRendering( + aNameSpaceID, aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + aAttribute == nsGkAtoms::diffuseConstant); +} + +nsresult SVGFEDiffuseLightingElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feDiffuseLighting); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEDiffuseLightingElement.h b/dom/svg/SVGFEDiffuseLightingElement.h new file mode 100644 index 0000000000..1e70afa3c4 --- /dev/null +++ b/dom/svg/SVGFEDiffuseLightingElement.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEDIFFUSELIGHTINGELEMENT_H_ +#define DOM_SVG_SVGFEDIFFUSELIGHTINGELEMENT_H_ + +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEDiffuseLightingElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEDiffuseLightingElementBase = SVGFELightingElement; + +class SVGFEDiffuseLightingElement final + : public SVGFEDiffuseLightingElementBase { + friend nsresult(::NS_NewSVGFEDiffuseLightingElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEDiffuseLightingElement( + already_AddRefed&& aNodeInfo) + : SVGFEDiffuseLightingElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + // WebIDL + already_AddRefed In1(); + already_AddRefed SurfaceScale(); + already_AddRefed DiffuseConstant(); + already_AddRefed KernelUnitLengthX(); + already_AddRefed KernelUnitLengthY(); +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEDIFFUSELIGHTINGELEMENT_H_ diff --git a/dom/svg/SVGFEDisplacementMapElement.cpp b/dom/svg/SVGFEDisplacementMapElement.cpp new file mode 100644 index 0000000000..8bbe5ebc7a --- /dev/null +++ b/dom/svg/SVGFEDisplacementMapElement.cpp @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEDisplacementMapElement.h" +#include "mozilla/dom/SVGFEDisplacementMapElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEDisplacementMap) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEDisplacementMapElement::WrapNode( + JSContext* aCx, JS::Handle aGivenProto) { + return SVGFEDisplacementMapElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFEDisplacementMapElement::sNumberInfo[1] = { + {nsGkAtoms::scale, 0, false}, +}; + +SVGEnumMapping SVGFEDisplacementMapElement::sChannelMap[] = { + {nsGkAtoms::R, SVG_CHANNEL_R}, + {nsGkAtoms::G, SVG_CHANNEL_G}, + {nsGkAtoms::B, SVG_CHANNEL_B}, + {nsGkAtoms::A, SVG_CHANNEL_A}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGFEDisplacementMapElement::sEnumInfo[2] = { + {nsGkAtoms::xChannelSelector, sChannelMap, SVG_CHANNEL_A}, + {nsGkAtoms::yChannelSelector, sChannelMap, SVG_CHANNEL_A}}; + +SVGElement::StringInfo SVGFEDisplacementMapElement::sStringInfo[3] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}, + {nsGkAtoms::in2, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEDisplacementMapElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGFEDisplacementMapElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed SVGFEDisplacementMapElement::In2() { + return mStringAttributes[IN2].ToDOMAnimatedString(this); +} + +already_AddRefed SVGFEDisplacementMapElement::Scale() { + return mNumberAttributes[SCALE].ToDOMAnimatedNumber(this); +} + +already_AddRefed +SVGFEDisplacementMapElement::XChannelSelector() { + return mEnumAttributes[CHANNEL_X].ToDOMAnimatedEnum(this); +} + +already_AddRefed +SVGFEDisplacementMapElement::YChannelSelector() { + return mEnumAttributes[CHANNEL_Y].ToDOMAnimatedEnum(this); +} + +FilterPrimitiveDescription SVGFEDisplacementMapElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + if (aInputsAreTainted[1]) { + // If the map is tainted, refuse to apply the effect and act as a + // pass-through filter instead, as required by the spec. + OffsetAttributes atts; + atts.mValue = IntPoint(0, 0); + return FilterPrimitiveDescription(AsVariant(std::move(atts))); + } + + float scale = aInstance->GetPrimitiveNumber(SVGContentUtils::XY, + &mNumberAttributes[SCALE]); + uint32_t xChannel = mEnumAttributes[CHANNEL_X].GetAnimValue(); + uint32_t yChannel = mEnumAttributes[CHANNEL_Y].GetAnimValue(); + DisplacementMapAttributes atts; + atts.mScale = scale; + atts.mXChannel = xChannel; + atts.mYChannel = yChannel; + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEDisplacementMapElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFEDisplacementMapElementBase::AttributeAffectsRendering( + aNameSpaceID, aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || aAttribute == nsGkAtoms::in2 || + aAttribute == nsGkAtoms::scale || + aAttribute == nsGkAtoms::xChannelSelector || + aAttribute == nsGkAtoms::yChannelSelector)); +} + +void SVGFEDisplacementMapElement::GetSourceImageNames( + nsTArray& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN2], this)); +} + +nsresult SVGFEDisplacementMapElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feDisplacementMap); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFEDisplacementMapElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +SVGElement::EnumAttributesInfo SVGFEDisplacementMapElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGFEDisplacementMapElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEDisplacementMapElement.h b/dom/svg/SVGFEDisplacementMapElement.h new file mode 100644 index 0000000000..408cc42994 --- /dev/null +++ b/dom/svg/SVGFEDisplacementMapElement.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEDISPLACEMENTMAPELEMENT_H_ +#define DOM_SVG_SVGFEDISPLACEMENTMAPELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEDisplacementMapElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEDisplacementMapElementBase = SVGFE; + +class SVGFEDisplacementMapElement final + : public SVGFEDisplacementMapElementBase { + protected: + friend nsresult(::NS_NewSVGFEDisplacementMapElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGFEDisplacementMapElement( + already_AddRefed&& aNodeInfo) + : SVGFEDisplacementMapElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + // WebIDL + already_AddRefed In1(); + already_AddRefed In2(); + already_AddRefed Scale(); + already_AddRefed XChannelSelector(); + already_AddRefed YChannelSelector(); + + protected: + virtual bool OperatesOnSRGB(int32_t aInputIndex, + bool aInputIsAlreadySRGB) override { + switch (aInputIndex) { + case 0: + return aInputIsAlreadySRGB; + case 1: + return SVGFEDisplacementMapElementBase::OperatesOnSRGB( + aInputIndex, aInputIsAlreadySRGB); + default: + NS_ERROR("Will not give correct color model"); + return false; + } + } + + NumberAttributesInfo GetNumberInfo() override; + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { SCALE }; + SVGAnimatedNumber mNumberAttributes[1]; + static NumberInfo sNumberInfo[1]; + + enum { CHANNEL_X, CHANNEL_Y }; + SVGAnimatedEnumeration mEnumAttributes[2]; + static SVGEnumMapping sChannelMap[]; + static EnumInfo sEnumInfo[2]; + + enum { RESULT, IN1, IN2 }; + SVGAnimatedString mStringAttributes[3]; + static StringInfo sStringInfo[3]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEDISPLACEMENTMAPELEMENT_H_ diff --git a/dom/svg/SVGFEDistantLightElement.cpp b/dom/svg/SVGFEDistantLightElement.cpp new file mode 100644 index 0000000000..f2b5b19c33 --- /dev/null +++ b/dom/svg/SVGFEDistantLightElement.cpp @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEDistantLightElement.h" +#include "mozilla/dom/SVGFEDistantLightElementBinding.h" +#include "mozilla/SVGFilterInstance.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEDistantLight) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEDistantLightElement::WrapNode( + JSContext* aCx, JS::Handle aGivenProto) { + return SVGFEDistantLightElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFEDistantLightElement::sNumberInfo[2] = { + {nsGkAtoms::azimuth, 0, false}, {nsGkAtoms::elevation, 0, false}}; + +//---------------------------------------------------------------------- +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEDistantLightElement) + +// nsFEUnstyledElement methods + +bool SVGFEDistantLightElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::azimuth || + aAttribute == nsGkAtoms::elevation); +} + +LightType SVGFEDistantLightElement::ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray& aFloatAttributes) { + float azimuth, elevation; + GetAnimatedNumberValues(&azimuth, &elevation, nullptr); + + aFloatAttributes.SetLength(kDistantLightNumAttributes); + aFloatAttributes[kDistantLightAzimuthIndex] = azimuth; + aFloatAttributes[kDistantLightElevationIndex] = elevation; + return LightType::Distant; +} + +already_AddRefed SVGFEDistantLightElement::Azimuth() { + return mNumberAttributes[AZIMUTH].ToDOMAnimatedNumber(this); +} + +already_AddRefed SVGFEDistantLightElement::Elevation() { + return mNumberAttributes[ELEVATION].ToDOMAnimatedNumber(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFEDistantLightElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEDistantLightElement.h b/dom/svg/SVGFEDistantLightElement.h new file mode 100644 index 0000000000..5eb98ed12e --- /dev/null +++ b/dom/svg/SVGFEDistantLightElement.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEDISTANTLIGHTELEMENT_H_ +#define DOM_SVG_SVGFEDISTANTLIGHTELEMENT_H_ + +#include "SVGAnimatedNumber.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEDistantLightElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEDistantLightElementBase = SVGFELightElement; + +class SVGFEDistantLightElement final : public SVGFEDistantLightElementBase { + friend nsresult(::NS_NewSVGFEDistantLightElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEDistantLightElement( + already_AddRefed&& aNodeInfo) + : SVGFEDistantLightElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + mozilla::gfx::LightType ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray& aFloatAttributes) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed Azimuth(); + already_AddRefed Elevation(); + + protected: + NumberAttributesInfo GetNumberInfo() override; + + enum { AZIMUTH, ELEVATION }; + SVGAnimatedNumber mNumberAttributes[2]; + static NumberInfo sNumberInfo[2]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEDISTANTLIGHTELEMENT_H_ diff --git a/dom/svg/SVGFEDropShadowElement.cpp b/dom/svg/SVGFEDropShadowElement.cpp new file mode 100644 index 0000000000..275f6003b1 --- /dev/null +++ b/dom/svg/SVGFEDropShadowElement.cpp @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEDropShadowElement.h" +#include "mozilla/dom/SVGFEDropShadowElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "nsIFrame.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEDropShadow) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEDropShadowElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFEDropShadowElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFEDropShadowElement::sNumberInfo[2] = { + {nsGkAtoms::dx, 2, false}, {nsGkAtoms::dy, 2, false}}; + +SVGElement::NumberPairInfo SVGFEDropShadowElement::sNumberPairInfo[1] = { + {nsGkAtoms::stdDeviation, 2, 2}}; + +SVGElement::StringInfo SVGFEDropShadowElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEDropShadowElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGFEDropShadowElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed SVGFEDropShadowElement::Dx() { + return mNumberAttributes[DX].ToDOMAnimatedNumber(this); +} + +already_AddRefed SVGFEDropShadowElement::Dy() { + return mNumberAttributes[DY].ToDOMAnimatedNumber(this); +} + +already_AddRefed SVGFEDropShadowElement::StdDeviationX() { + return mNumberPairAttributes[STD_DEV].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eFirst, this); +} + +already_AddRefed SVGFEDropShadowElement::StdDeviationY() { + return mNumberPairAttributes[STD_DEV].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eSecond, this); +} + +void SVGFEDropShadowElement::SetStdDeviation(float stdDeviationX, + float stdDeviationY) { + mNumberPairAttributes[STD_DEV].SetBaseValues(stdDeviationX, stdDeviationY, + this); +} + +FilterPrimitiveDescription SVGFEDropShadowElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + float stdX = aInstance->GetPrimitiveNumber(SVGContentUtils::X, + &mNumberPairAttributes[STD_DEV], + SVGAnimatedNumberPair::eFirst); + float stdY = aInstance->GetPrimitiveNumber(SVGContentUtils::Y, + &mNumberPairAttributes[STD_DEV], + SVGAnimatedNumberPair::eSecond); + if (stdX < 0 || stdY < 0) { + return FilterPrimitiveDescription(); + } + + Point offset( + aInstance->GetPrimitiveNumber(SVGContentUtils::X, &mNumberAttributes[DX]), + aInstance->GetPrimitiveNumber(SVGContentUtils::Y, + &mNumberAttributes[DY])); + + DropShadowAttributes atts; + atts.mStdDeviation = Size(stdX, stdY); + atts.mOffset = offset; + + nsIFrame* frame = GetPrimaryFrame(); + if (frame) { + const nsStyleSVGReset* styleSVGReset = frame->Style()->StyleSVGReset(); + sRGBColor color( + sRGBColor::FromABGR(styleSVGReset->mFloodColor.CalcColor(frame))); + color.a *= styleSVGReset->mFloodOpacity; + atts.mColor = color; + } else { + atts.mColor = sRGBColor(); + } + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEDropShadowElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFEDropShadowElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || + aAttribute == nsGkAtoms::stdDeviation || + aAttribute == nsGkAtoms::dx || aAttribute == nsGkAtoms::dy)); +} + +void SVGFEDropShadowElement::GetSourceImageNames( + nsTArray& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFEDropShadowElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +SVGElement::NumberPairAttributesInfo +SVGFEDropShadowElement::GetNumberPairInfo() { + return NumberPairAttributesInfo(mNumberPairAttributes, sNumberPairInfo, + ArrayLength(sNumberPairInfo)); +} + +SVGElement::StringAttributesInfo SVGFEDropShadowElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEDropShadowElement.h b/dom/svg/SVGFEDropShadowElement.h new file mode 100644 index 0000000000..6f0e12fb9c --- /dev/null +++ b/dom/svg/SVGFEDropShadowElement.h @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEDROPSHADOWELEMENT_H_ +#define DOM_SVG_SVGFEDROPSHADOWELEMENT_H_ + +#include "SVGAnimatedNumber.h" +#include "SVGAnimatedNumberPair.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEDropShadowElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEDropShadowElementBase = SVGFE; + +class SVGFEDropShadowElement final : public SVGFEDropShadowElementBase { + friend nsresult(::NS_NewSVGFEDropShadowElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEDropShadowElement( + already_AddRefed&& aNodeInfo) + : SVGFEDropShadowElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray& aSources) override; + + // nsIContent interface + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed In1(); + already_AddRefed Dx(); + already_AddRefed Dy(); + already_AddRefed StdDeviationX(); + already_AddRefed StdDeviationY(); + void SetStdDeviation(float stdDeviationX, float stdDeviationY); + + protected: + NumberAttributesInfo GetNumberInfo() override; + NumberPairAttributesInfo GetNumberPairInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { DX, DY }; + SVGAnimatedNumber mNumberAttributes[2]; + static NumberInfo sNumberInfo[2]; + + enum { STD_DEV }; + SVGAnimatedNumberPair mNumberPairAttributes[1]; + static NumberPairInfo sNumberPairInfo[1]; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEDROPSHADOWELEMENT_H_ diff --git a/dom/svg/SVGFEFloodElement.cpp b/dom/svg/SVGFEFloodElement.cpp new file mode 100644 index 0000000000..3724ae3c75 --- /dev/null +++ b/dom/svg/SVGFEFloodElement.cpp @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEFloodElement.h" + +#include "FilterSupport.h" +#include "mozilla/dom/SVGFEFloodElementBinding.h" +#include "nsColor.h" +#include "nsIFrame.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEFlood) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEFloodElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFEFloodElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGFEFloodElement::sStringInfo[1] = { + {nsGkAtoms::result, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEFloodElement) + +FilterPrimitiveDescription SVGFEFloodElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + FloodAttributes atts; + nsIFrame* frame = GetPrimaryFrame(); + if (frame) { + const nsStyleSVGReset* styleSVGReset = frame->Style()->StyleSVGReset(); + sRGBColor color( + sRGBColor::FromABGR(styleSVGReset->mFloodColor.CalcColor(frame))); + color.a *= styleSVGReset->mFloodOpacity; + atts.mColor = color; + } else { + atts.mColor = sRGBColor(); + } + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +nsresult SVGFEFloodElement::BindToTree(BindContext& aCtx, nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feFlood); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::StringAttributesInfo SVGFEFloodElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEFloodElement.h b/dom/svg/SVGFEFloodElement.h new file mode 100644 index 0000000000..1755ff855c --- /dev/null +++ b/dom/svg/SVGFEFloodElement.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEFLOODELEMENT_H_ +#define DOM_SVG_SVGFEFLOODELEMENT_H_ + +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEFloodElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEFloodElementBase = SVGFE; + +class SVGFEFloodElement final : public SVGFEFloodElementBase { + friend nsresult(::NS_NewSVGFEFloodElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEFloodElement( + already_AddRefed&& aNodeInfo) + : SVGFEFloodElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + + public: + bool SubregionIsUnionOfRegions() override { return false; } + + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + + // nsIContent interface + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + protected: + bool ProducesSRGB() override { return true; } + + StringAttributesInfo GetStringInfo() override; + + enum { RESULT }; + SVGAnimatedString mStringAttributes[1]; + static StringInfo sStringInfo[1]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEFLOODELEMENT_H_ diff --git a/dom/svg/SVGFEGaussianBlurElement.cpp b/dom/svg/SVGFEGaussianBlurElement.cpp new file mode 100644 index 0000000000..2bc0c95eae --- /dev/null +++ b/dom/svg/SVGFEGaussianBlurElement.cpp @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEGaussianBlurElement.h" +#include "mozilla/dom/SVGFEGaussianBlurElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEGaussianBlur) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEGaussianBlurElement::WrapNode( + JSContext* aCx, JS::Handle aGivenProto) { + return SVGFEGaussianBlurElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberPairInfo SVGFEGaussianBlurElement::sNumberPairInfo[1] = { + {nsGkAtoms::stdDeviation, 0, 0}}; + +SVGElement::StringInfo SVGFEGaussianBlurElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEGaussianBlurElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGFEGaussianBlurElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed +SVGFEGaussianBlurElement::StdDeviationX() { + return mNumberPairAttributes[STD_DEV].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eFirst, this); +} + +already_AddRefed +SVGFEGaussianBlurElement::StdDeviationY() { + return mNumberPairAttributes[STD_DEV].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eSecond, this); +} + +void SVGFEGaussianBlurElement::SetStdDeviation(float stdDeviationX, + float stdDeviationY) { + mNumberPairAttributes[STD_DEV].SetBaseValues(stdDeviationX, stdDeviationY, + this); +} + +FilterPrimitiveDescription SVGFEGaussianBlurElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + float stdX = aInstance->GetPrimitiveNumber(SVGContentUtils::X, + &mNumberPairAttributes[STD_DEV], + SVGAnimatedNumberPair::eFirst); + float stdY = aInstance->GetPrimitiveNumber(SVGContentUtils::Y, + &mNumberPairAttributes[STD_DEV], + SVGAnimatedNumberPair::eSecond); + if (stdX < 0 || stdY < 0) { + return FilterPrimitiveDescription(); + } + + GaussianBlurAttributes atts; + atts.mStdDeviation = Size(stdX, stdY); + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEGaussianBlurElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFEGaussianBlurElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || + aAttribute == nsGkAtoms::stdDeviation)); +} + +void SVGFEGaussianBlurElement::GetSourceImageNames( + nsTArray& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +nsresult SVGFEGaussianBlurElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feGaussianBlur); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberPairAttributesInfo +SVGFEGaussianBlurElement::GetNumberPairInfo() { + return NumberPairAttributesInfo(mNumberPairAttributes, sNumberPairInfo, + ArrayLength(sNumberPairInfo)); +} + +SVGElement::StringAttributesInfo SVGFEGaussianBlurElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEGaussianBlurElement.h b/dom/svg/SVGFEGaussianBlurElement.h new file mode 100644 index 0000000000..9980aea714 --- /dev/null +++ b/dom/svg/SVGFEGaussianBlurElement.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEGAUSSIANBLURELEMENT_H_ +#define DOM_SVG_SVGFEGAUSSIANBLURELEMENT_H_ + +#include "SVGAnimatedNumberPair.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEGaussianBlurElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEGaussianBlurElementBase = SVGFE; + +class SVGFEGaussianBlurElement final : public SVGFEGaussianBlurElementBase { + friend nsresult(::NS_NewSVGFEGaussianBlurElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEGaussianBlurElement( + already_AddRefed&& aNodeInfo) + : SVGFEGaussianBlurElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + // WebIDL + already_AddRefed In1(); + already_AddRefed StdDeviationX(); + already_AddRefed StdDeviationY(); + void SetStdDeviation(float stdDeviationX, float stdDeviationY); + + protected: + NumberPairAttributesInfo GetNumberPairInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { STD_DEV }; + SVGAnimatedNumberPair mNumberPairAttributes[1]; + static NumberPairInfo sNumberPairInfo[1]; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEGAUSSIANBLURELEMENT_H_ diff --git a/dom/svg/SVGFEImageElement.cpp b/dom/svg/SVGFEImageElement.cpp new file mode 100644 index 0000000000..733c66835f --- /dev/null +++ b/dom/svg/SVGFEImageElement.cpp @@ -0,0 +1,375 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEImageElement.h" + +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" +#include "mozilla/dom/SVGFEImageElementBinding.h" +#include "mozilla/dom/SVGFilterElement.h" +#include "mozilla/dom/UserActivation.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "nsContentUtils.h" +#include "nsLayoutUtils.h" +#include "nsNetUtil.h" +#include "imgIContainer.h" +#include "gfx2DGlue.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEImage) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEImageElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFEImageElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGFEImageElement::sStringInfo[3] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_XLink, true}}; + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ISUPPORTS_INHERITED(SVGFEImageElement, SVGFEImageElementBase, + imgINotificationObserver, nsIImageLoadingContent) + +//---------------------------------------------------------------------- +// Implementation + +SVGFEImageElement::SVGFEImageElement( + already_AddRefed&& aNodeInfo) + : SVGFEImageElementBase(std::move(aNodeInfo)), mImageAnimationMode(0) { + // We start out broken + AddStatesSilently(ElementState::BROKEN); +} + +SVGFEImageElement::~SVGFEImageElement() { nsImageLoadingContent::Destroy(); } + +//---------------------------------------------------------------------- + +nsresult SVGFEImageElement::LoadSVGImage(bool aForce, bool aNotify) { + // resolve href attribute + nsIURI* baseURI = GetBaseURI(); + + nsAutoString href; + if (mStringAttributes[HREF].IsExplicitlySet()) { + mStringAttributes[HREF].GetAnimValue(href, this); + } else { + mStringAttributes[XLINK_HREF].GetAnimValue(href, this); + } + href.Trim(" \t\n\r"); + + if (baseURI && !href.IsEmpty()) NS_MakeAbsoluteURI(href, href, baseURI); + + // Make sure we don't get in a recursive death-spiral + Document* doc = OwnerDoc(); + nsCOMPtr hrefAsURI; + if (NS_SUCCEEDED(StringToURI(href, doc, getter_AddRefs(hrefAsURI)))) { + bool isEqual; + if (NS_SUCCEEDED(hrefAsURI->Equals(baseURI, &isEqual)) && isEqual) { + // Image URI matches our URI exactly! Bail out. + return NS_OK; + } + } + + // Mark channel as urgent-start before load image if the image load is + // initaiated by a user interaction. + mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput(); + return LoadImage(href, aForce, aNotify, eImageLoadType_Normal); +} + +bool SVGFEImageElement::ShouldLoadImage() const { + return LoadingEnabled() && OwnerDoc()->ShouldLoadImages(); +} + +//---------------------------------------------------------------------- +// EventTarget methods: + +void SVGFEImageElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) { + nsImageLoadingContent::AsyncEventRunning(aEvent); +} + +//---------------------------------------------------------------------- +// nsIContent methods: + +bool SVGFEImageElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) { + if (aNamespaceID == kNameSpaceID_None && + aAttribute == nsGkAtoms::crossorigin) { + ParseCORSValue(aValue, aResult); + return true; + } + return SVGFEImageElementBase::ParseAttribute( + aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult); +} + +void SVGFEImageElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, + bool aNotify) { + if (aName == nsGkAtoms::href && (aNamespaceID == kNameSpaceID_XLink || + aNamespaceID == kNameSpaceID_None)) { + if (aValue) { + if (ShouldLoadImage()) { + LoadSVGImage(true, aNotify); + } + } else { + CancelImageRequests(aNotify); + } + } else if (aNamespaceID == kNameSpaceID_None && + aName == nsGkAtoms::crossorigin) { + if (aNotify && GetCORSMode() != AttrValueToCORSMode(aOldValue) && + ShouldLoadImage()) { + ForceReload(aNotify, IgnoreErrors()); + } + } + + return SVGFEImageElementBase::AfterSetAttr( + aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); +} + +void SVGFEImageElement::MaybeLoadSVGImage() { + if ((mStringAttributes[HREF].IsExplicitlySet() || + mStringAttributes[XLINK_HREF].IsExplicitlySet()) && + (NS_FAILED(LoadSVGImage(false, true)) || !LoadingEnabled())) { + CancelImageRequests(true); + } +} + +nsresult SVGFEImageElement::BindToTree(BindContext& aContext, + nsINode& aParent) { + nsresult rv = SVGFEImageElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + nsImageLoadingContent::BindToTree(aContext, aParent); + + if ((mStringAttributes[HREF].IsExplicitlySet() || + mStringAttributes[XLINK_HREF].IsExplicitlySet()) && + ShouldLoadImage()) { + nsContentUtils::AddScriptRunner( + NewRunnableMethod("dom::SVGFEImageElement::MaybeLoadSVGImage", this, + &SVGFEImageElement::MaybeLoadSVGImage)); + } + + if (aContext.InComposedDoc()) { + aContext.OwnerDoc().SetUseCounter(eUseCounter_custom_feImage); + } + + return rv; +} + +void SVGFEImageElement::UnbindFromTree(bool aNullParent) { + nsImageLoadingContent::UnbindFromTree(aNullParent); + SVGFEImageElementBase::UnbindFromTree(aNullParent); +} + +ElementState SVGFEImageElement::IntrinsicState() const { + return SVGFEImageElementBase::IntrinsicState() | + nsImageLoadingContent::ImageState(); +} + +void SVGFEImageElement::DestroyContent() { + nsImageLoadingContent::Destroy(); + SVGFEImageElementBase::DestroyContent(); +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEImageElement) + +already_AddRefed SVGFEImageElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// nsImageLoadingContent methods: + +CORSMode SVGFEImageElement::GetCORSMode() { + return AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin)); +} + +//---------------------------------------------------------------------- +// nsIDOMSVGFEImageElement methods + +FilterPrimitiveDescription SVGFEImageElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + nsIFrame* frame = GetPrimaryFrame(); + if (!frame) { + return FilterPrimitiveDescription(); + } + + nsCOMPtr currentRequest; + GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(currentRequest)); + + nsCOMPtr imageContainer; + if (currentRequest) { + currentRequest->GetImage(getter_AddRefs(imageContainer)); + } + + RefPtr image; + if (imageContainer) { + uint32_t flags = + imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY; + image = imageContainer->GetFrame(imgIContainer::FRAME_CURRENT, flags); + } + + if (!image) { + return FilterPrimitiveDescription(); + } + + IntSize nativeSize; + imageContainer->GetWidth(&nativeSize.width); + imageContainer->GetHeight(&nativeSize.height); + + Matrix viewBoxTM = SVGContentUtils::GetViewBoxTransform( + aFilterSubregion.width, aFilterSubregion.height, 0, 0, nativeSize.width, + nativeSize.height, mPreserveAspectRatio); + Matrix TM = viewBoxTM; + TM.PostTranslate(aFilterSubregion.x, aFilterSubregion.y); + + SamplingFilter samplingFilter = + nsLayoutUtils::GetSamplingFilterForFrame(frame); + + ImageAttributes atts; + atts.mFilter = (uint32_t)samplingFilter; + atts.mTransform = TM; + + // Append the image to aInputImages and store its index in the description. + size_t imageIndex = aInputImages.Length(); + aInputImages.AppendElement(image); + atts.mInputIndex = (uint32_t)imageIndex; + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEImageElement::AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const { + // nsGkAtoms::href is deliberately omitted as the frame has special + // handling to load the image + return SVGFEImageElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + aAttribute == nsGkAtoms::preserveAspectRatio); +} + +bool SVGFEImageElement::OutputIsTainted(const nsTArray& aInputsAreTainted, + nsIPrincipal* aReferencePrincipal) { + nsresult rv; + nsCOMPtr currentRequest; + GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(currentRequest)); + + if (!currentRequest) { + return false; + } + + nsCOMPtr principal; + rv = currentRequest->GetImagePrincipal(getter_AddRefs(principal)); + if (NS_FAILED(rv) || !principal) { + return true; + } + + // If CORS was used to load the image, the page is allowed to read from it. + if (nsLayoutUtils::ImageRequestUsesCORS(currentRequest)) { + return false; + } + + if (aReferencePrincipal->Subsumes(principal)) { + // The page is allowed to read from the image. + return false; + } + + return true; +} + +//---------------------------------------------------------------------- +// SVGElement methods + +already_AddRefed +SVGFEImageElement::PreserveAspectRatio() { + return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this); +} + +SVGAnimatedPreserveAspectRatio* +SVGFEImageElement::GetAnimatedPreserveAspectRatio() { + return &mPreserveAspectRatio; +} + +SVGElement::StringAttributesInfo SVGFEImageElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +//---------------------------------------------------------------------- +// nsIImageLoadingContent methods +NS_IMETHODIMP_(void) +SVGFEImageElement::FrameCreated(nsIFrame* aFrame) { + nsImageLoadingContent::FrameCreated(aFrame); + + uint64_t mode = aFrame->PresContext()->ImageAnimationMode(); + if (mode == mImageAnimationMode) { + return; + } + + mImageAnimationMode = mode; + + if (mPendingRequest) { + nsCOMPtr container; + mPendingRequest->GetImage(getter_AddRefs(container)); + if (container) { + container->SetAnimationMode(mode); + } + } + + if (mCurrentRequest) { + nsCOMPtr container; + mCurrentRequest->GetImage(getter_AddRefs(container)); + if (container) { + container->SetAnimationMode(mode); + } + } +} + +//---------------------------------------------------------------------- +// imgINotificationObserver methods + +void SVGFEImageElement::Notify(imgIRequest* aRequest, int32_t aType, + const nsIntRect* aData) { + nsImageLoadingContent::Notify(aRequest, aType, aData); + + if (aType == imgINotificationObserver::SIZE_AVAILABLE) { + // Request a decode + nsCOMPtr container; + aRequest->GetImage(getter_AddRefs(container)); + MOZ_ASSERT(container, "who sent the notification then?"); + container->StartDecoding(imgIContainer::FLAG_NONE); + container->SetAnimationMode(mImageAnimationMode); + } + + if (aType == imgINotificationObserver::LOAD_COMPLETE || + aType == imgINotificationObserver::FRAME_UPDATE || + aType == imgINotificationObserver::SIZE_AVAILABLE) { + if (auto* filter = SVGFilterElement::FromNodeOrNull(GetParent())) { + SVGObserverUtils::InvalidateDirectRenderingObservers(filter); + } + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEImageElement.h b/dom/svg/SVGFEImageElement.h new file mode 100644 index 0000000000..4c80554031 --- /dev/null +++ b/dom/svg/SVGFEImageElement.h @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEIMAGEELEMENT_H_ +#define DOM_SVG_SVGFEIMAGEELEMENT_H_ + +#include "mozilla/dom/SVGFilters.h" +#include "SVGAnimatedPreserveAspectRatio.h" + +nsresult NS_NewSVGFEImageElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla { +class SVGFEImageFrame; + +namespace dom { + +using SVGFEImageElementBase = SVGFE; + +class SVGFEImageElement final : public SVGFEImageElementBase, + public nsImageLoadingContent { + friend class mozilla::SVGFEImageFrame; + + protected: + friend nsresult(::NS_NewSVGFEImageElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGFEImageElement( + already_AddRefed&& aNodeInfo); + virtual ~SVGFEImageElement(); + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + bool SubregionIsUnionOfRegions() override { return false; } + + // interfaces: + NS_DECL_ISUPPORTS_INHERITED + + // EventTarget + void AsyncEventRunning(AsyncEventDispatcher* aEvent) override; + + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + + // nsImageLoadingContent + CORSMode GetCORSMode() override; + + bool OutputIsTainted(const nsTArray& aInputsAreTainted, + nsIPrincipal* aReferencePrincipal) override; + + // nsIContent + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) override; + void AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, bool aNotify) override; + nsresult BindToTree(BindContext&, nsINode& aParent) override; + void UnbindFromTree(bool aNullParent) override; + ElementState IntrinsicState() const override; + void DestroyContent() override; + + NS_DECL_IMGINOTIFICATIONOBSERVER + + // Override for nsIImageLoadingContent. + NS_IMETHOD_(void) FrameCreated(nsIFrame* aFrame) override; + + void MaybeLoadSVGImage(); + + // WebIDL + already_AddRefed Href(); + already_AddRefed PreserveAspectRatio(); + void GetCrossOrigin(nsAString& aCrossOrigin) { + // Null for both missing and invalid defaults is ok, since we + // always parse to an enum value, so we don't need an invalid + // default, and we _want_ the missing default to be null. + GetEnumAttr(nsGkAtoms::crossorigin, nullptr, aCrossOrigin); + } + void SetCrossOrigin(const nsAString& aCrossOrigin, ErrorResult& aError) { + SetOrRemoveNullableStringAttr(nsGkAtoms::crossorigin, aCrossOrigin, aError); + } + + private: + nsresult LoadSVGImage(bool aForce, bool aNotify); + bool ShouldLoadImage() const; + + protected: + bool ProducesSRGB() override { return true; } + + SVGAnimatedPreserveAspectRatio* GetAnimatedPreserveAspectRatio() override; + StringAttributesInfo GetStringInfo() override; + + // Override for nsImageLoadingContent. + nsIContent* AsContent() override { return this; } + + enum { RESULT, HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[3]; + static StringInfo sStringInfo[3]; + + SVGAnimatedPreserveAspectRatio mPreserveAspectRatio; + uint16_t mImageAnimationMode; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGFEIMAGEELEMENT_H_ diff --git a/dom/svg/SVGFEMergeElement.cpp b/dom/svg/SVGFEMergeElement.cpp new file mode 100644 index 0000000000..f9c73c3069 --- /dev/null +++ b/dom/svg/SVGFEMergeElement.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEMergeElement.h" +#include "mozilla/dom/SVGFEMergeElementBinding.h" +#include "mozilla/dom/SVGFEMergeNodeElement.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEMerge) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEMergeElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFEMergeElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGFEMergeElement::sStringInfo[1] = { + {nsGkAtoms::result, kNameSpaceID_None, true}}; + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEMergeElement) + +FilterPrimitiveDescription SVGFEMergeElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + return FilterPrimitiveDescription(AsVariant(MergeAttributes())); +} + +void SVGFEMergeElement::GetSourceImageNames(nsTArray& aSources) { + for (nsIContent* child = nsINode::GetFirstChild(); child; + child = child->GetNextSibling()) { + if (auto* node = SVGFEMergeNodeElement::FromNode(child)) { + aSources.AppendElement(SVGStringInfo(node->GetIn1(), node)); + } + } +} + +nsresult SVGFEMergeElement::BindToTree(BindContext& aCtx, nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feMerge); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::StringAttributesInfo SVGFEMergeElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEMergeElement.h b/dom/svg/SVGFEMergeElement.h new file mode 100644 index 0000000000..1115531ac5 --- /dev/null +++ b/dom/svg/SVGFEMergeElement.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEMERGEELEMENT_H_ +#define DOM_SVG_SVGFEMERGEELEMENT_H_ + +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEMergeElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEMergeElementBase = SVGFE; + +class SVGFEMergeElement final : public SVGFEMergeElementBase { + friend nsresult(::NS_NewSVGFEMergeElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEMergeElement( + already_AddRefed&& aNodeInfo) + : SVGFEMergeElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray& aSources) override; + + // nsIContent + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + protected: + StringAttributesInfo GetStringInfo() override; + + enum { RESULT }; + SVGAnimatedString mStringAttributes[1]; + static StringInfo sStringInfo[1]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEMERGEELEMENT_H_ diff --git a/dom/svg/SVGFEMergeNodeElement.cpp b/dom/svg/SVGFEMergeNodeElement.cpp new file mode 100644 index 0000000000..21c07f6421 --- /dev/null +++ b/dom/svg/SVGFEMergeNodeElement.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEMergeNodeElement.h" +#include "mozilla/dom/SVGFEMergeNodeElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEMergeNode) + +namespace mozilla::dom { + +JSObject* SVGFEMergeNodeElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFEMergeNodeElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGFEMergeNodeElement::sStringInfo[1] = { + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEMergeNodeElement) + +//---------------------------------------------------------------------- +// nsFEUnstyledElement methods + +bool SVGFEMergeNodeElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::in; +} + +already_AddRefed SVGFEMergeNodeElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::StringAttributesInfo SVGFEMergeNodeElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEMergeNodeElement.h b/dom/svg/SVGFEMergeNodeElement.h new file mode 100644 index 0000000000..354767e5e1 --- /dev/null +++ b/dom/svg/SVGFEMergeNodeElement.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEMERGENODEELEMENT_H_ +#define DOM_SVG_SVGFEMERGENODEELEMENT_H_ + +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEMergeNodeElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEMergeNodeElementBase = SVGFEUnstyledElement; + +class SVGFEMergeNodeElement final : public SVGFEMergeNodeElementBase { + friend nsresult(::NS_NewSVGFEMergeNodeElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEMergeNodeElement( + already_AddRefed&& aNodeInfo) + : SVGFEMergeNodeElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + NS_IMPL_FROMNODE_WITH_TAG(SVGFEMergeNodeElement, kNameSpaceID_SVG, + feMergeNode) + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + + const SVGAnimatedString* GetIn1() { return &mStringAttributes[IN1]; } + + // WebIDL + already_AddRefed In1(); + + protected: + StringAttributesInfo GetStringInfo() override; + + enum { IN1 }; + SVGAnimatedString mStringAttributes[1]; + static StringInfo sStringInfo[1]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEMERGENODEELEMENT_H_ diff --git a/dom/svg/SVGFEMorphologyElement.cpp b/dom/svg/SVGFEMorphologyElement.cpp new file mode 100644 index 0000000000..1ba882c06c --- /dev/null +++ b/dom/svg/SVGFEMorphologyElement.cpp @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEMorphologyElement.h" +#include "mozilla/dom/SVGFEMorphologyElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/BindContext.h" +#include "mozilla/dom/Document.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEMorphology) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEMorphologyElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFEMorphologyElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberPairInfo SVGFEMorphologyElement::sNumberPairInfo[1] = { + {nsGkAtoms::radius, 0, 0}}; + +SVGEnumMapping SVGFEMorphologyElement::sOperatorMap[] = { + {nsGkAtoms::erode, SVG_OPERATOR_ERODE}, + {nsGkAtoms::dilate, SVG_OPERATOR_DILATE}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGFEMorphologyElement::sEnumInfo[1] = { + {nsGkAtoms::_operator, sOperatorMap, SVG_OPERATOR_ERODE}}; + +SVGElement::StringInfo SVGFEMorphologyElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEMorphologyElement) + +//---------------------------------------------------------------------- +// SVGFEMorphologyElement methods + +already_AddRefed SVGFEMorphologyElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed SVGFEMorphologyElement::Operator() { + return mEnumAttributes[OPERATOR].ToDOMAnimatedEnum(this); +} + +already_AddRefed SVGFEMorphologyElement::RadiusX() { + return mNumberPairAttributes[RADIUS].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eFirst, this); +} + +already_AddRefed SVGFEMorphologyElement::RadiusY() { + return mNumberPairAttributes[RADIUS].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eSecond, this); +} + +void SVGFEMorphologyElement::SetRadius(float rx, float ry) { + mNumberPairAttributes[RADIUS].SetBaseValues(rx, ry, this); +} + +void SVGFEMorphologyElement::GetSourceImageNames( + nsTArray& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +#define MORPHOLOGY_EPSILON 0.0001 + +void SVGFEMorphologyElement::GetRXY(int32_t* aRX, int32_t* aRY, + const SVGFilterInstance& aInstance) { + // Subtract an epsilon here because we don't want a value that's just + // slightly larger than an integer to round up to the next integer; it's + // probably meant to be the integer it's close to, modulo machine precision + // issues. + *aRX = NSToIntCeil(aInstance.GetPrimitiveNumber( + SVGContentUtils::X, &mNumberPairAttributes[RADIUS], + SVGAnimatedNumberPair::eFirst) - + MORPHOLOGY_EPSILON); + *aRY = NSToIntCeil(aInstance.GetPrimitiveNumber( + SVGContentUtils::Y, &mNumberPairAttributes[RADIUS], + SVGAnimatedNumberPair::eSecond) - + MORPHOLOGY_EPSILON); +} + +FilterPrimitiveDescription SVGFEMorphologyElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + int32_t rx, ry; + GetRXY(&rx, &ry, *aInstance); + MorphologyAttributes atts; + atts.mRadii = Size(rx, ry); + atts.mOperator = (uint32_t)mEnumAttributes[OPERATOR].GetAnimValue(); + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEMorphologyElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFEMorphologyElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || aAttribute == nsGkAtoms::radius || + aAttribute == nsGkAtoms::_operator)); +} + +nsresult SVGFEMorphologyElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feMorphology); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberPairAttributesInfo +SVGFEMorphologyElement::GetNumberPairInfo() { + return NumberPairAttributesInfo(mNumberPairAttributes, sNumberPairInfo, + ArrayLength(sNumberPairInfo)); +} + +SVGElement::EnumAttributesInfo SVGFEMorphologyElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGFEMorphologyElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEMorphologyElement.h b/dom/svg/SVGFEMorphologyElement.h new file mode 100644 index 0000000000..2c0abe8e26 --- /dev/null +++ b/dom/svg/SVGFEMorphologyElement.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEMORPHOLOGYELEMENT_H_ +#define DOM_SVG_SVGFEMORPHOLOGYELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedNumberPair.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEMorphologyElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEMorphologyElementBase = SVGFE; + +class SVGFEMorphologyElement final : public SVGFEMorphologyElementBase { + friend nsresult(::NS_NewSVGFEMorphologyElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEMorphologyElement( + already_AddRefed&& aNodeInfo) + : SVGFEMorphologyElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + // WebIDL + already_AddRefed In1(); + already_AddRefed Operator(); + already_AddRefed RadiusX(); + already_AddRefed RadiusY(); + void SetRadius(float rx, float ry); + + protected: + void GetRXY(int32_t* aRX, int32_t* aRY, const SVGFilterInstance& aInstance); + + NumberPairAttributesInfo GetNumberPairInfo() override; + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + + void UpdateUseCounter() const; + + enum { RADIUS }; + SVGAnimatedNumberPair mNumberPairAttributes[1]; + static NumberPairInfo sNumberPairInfo[1]; + + enum { OPERATOR }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sOperatorMap[]; + static EnumInfo sEnumInfo[1]; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEMORPHOLOGYELEMENT_H_ diff --git a/dom/svg/SVGFEOffsetElement.cpp b/dom/svg/SVGFEOffsetElement.cpp new file mode 100644 index 0000000000..5ba5546d9f --- /dev/null +++ b/dom/svg/SVGFEOffsetElement.cpp @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEOffsetElement.h" +#include "mozilla/dom/SVGFEOffsetElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEOffset) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEOffsetElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFEOffsetElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFEOffsetElement::sNumberInfo[2] = { + {nsGkAtoms::dx, 0, false}, {nsGkAtoms::dy, 0, false}}; + +SVGElement::StringInfo SVGFEOffsetElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEOffsetElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGFEOffsetElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed SVGFEOffsetElement::Dx() { + return mNumberAttributes[DX].ToDOMAnimatedNumber(this); +} + +already_AddRefed SVGFEOffsetElement::Dy() { + return mNumberAttributes[DY].ToDOMAnimatedNumber(this); +} + +FilterPrimitiveDescription SVGFEOffsetElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + OffsetAttributes atts; + IntPoint offset(int32_t(aInstance->GetPrimitiveNumber( + SVGContentUtils::X, &mNumberAttributes[DX])), + int32_t(aInstance->GetPrimitiveNumber( + SVGContentUtils::Y, &mNumberAttributes[DY]))); + atts.mValue = offset; + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFEOffsetElement::AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const { + return SVGFEOffsetElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || aAttribute == nsGkAtoms::dx || + aAttribute == nsGkAtoms::dy)); +} + +void SVGFEOffsetElement::GetSourceImageNames( + nsTArray& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +nsresult SVGFEOffsetElement::BindToTree(BindContext& aCtx, nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feOffset); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFEOffsetElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +SVGElement::StringAttributesInfo SVGFEOffsetElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEOffsetElement.h b/dom/svg/SVGFEOffsetElement.h new file mode 100644 index 0000000000..08d5274801 --- /dev/null +++ b/dom/svg/SVGFEOffsetElement.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEOFFSETELEMENT_H_ +#define DOM_SVG_SVGFEOFFSETELEMENT_H_ + +#include "SVGAnimatedNumber.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEOffsetElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEOffsetElementBase = SVGFE; + +class SVGFEOffsetElement final : public SVGFEOffsetElementBase { + friend nsresult(::NS_NewSVGFEOffsetElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEOffsetElement( + already_AddRefed&& aNodeInfo) + : SVGFEOffsetElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + + // WebIDL + already_AddRefed In1(); + already_AddRefed Dx(); + already_AddRefed Dy(); + + protected: + NumberAttributesInfo GetNumberInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { DX, DY }; + SVGAnimatedNumber mNumberAttributes[2]; + static NumberInfo sNumberInfo[2]; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEOFFSETELEMENT_H_ diff --git a/dom/svg/SVGFEPointLightElement.cpp b/dom/svg/SVGFEPointLightElement.cpp new file mode 100644 index 0000000000..b8a8196f11 --- /dev/null +++ b/dom/svg/SVGFEPointLightElement.cpp @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFEPointLightElement.h" +#include "mozilla/dom/SVGFEPointLightElementBinding.h" +#include "mozilla/SVGFilterInstance.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEPointLight) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFEPointLightElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFEPointLightElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFEPointLightElement::sNumberInfo[3] = { + {nsGkAtoms::x, 0, false}, + {nsGkAtoms::y, 0, false}, + {nsGkAtoms::z, 0, false}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEPointLightElement) + +//---------------------------------------------------------------------- +// nsFEUnstyledElement methods + +bool SVGFEPointLightElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y || + aAttribute == nsGkAtoms::z); +} + +//---------------------------------------------------------------------- + +LightType SVGFEPointLightElement::ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray& aFloatAttributes) { + Point3D lightPos; + GetAnimatedNumberValues(&lightPos.x, &lightPos.y, &lightPos.z, nullptr); + lightPos = aInstance->ConvertLocation(lightPos); + aFloatAttributes.SetLength(kPointLightNumAttributes); + aFloatAttributes[kPointLightPositionXIndex] = lightPos.x; + aFloatAttributes[kPointLightPositionYIndex] = lightPos.y; + aFloatAttributes[kPointLightPositionZIndex] = lightPos.z; + return LightType::Point; +} + +already_AddRefed SVGFEPointLightElement::X() { + return mNumberAttributes[ATTR_X].ToDOMAnimatedNumber(this); +} + +already_AddRefed SVGFEPointLightElement::Y() { + return mNumberAttributes[ATTR_Y].ToDOMAnimatedNumber(this); +} + +already_AddRefed SVGFEPointLightElement::Z() { + return mNumberAttributes[ATTR_Z].ToDOMAnimatedNumber(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFEPointLightElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFEPointLightElement.h b/dom/svg/SVGFEPointLightElement.h new file mode 100644 index 0000000000..44a64e1522 --- /dev/null +++ b/dom/svg/SVGFEPointLightElement.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFEPOINTLIGHTELEMENT_H_ +#define DOM_SVG_SVGFEPOINTLIGHTELEMENT_H_ + +#include "SVGAnimatedNumber.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFEPointLightElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFEPointLightElementBase = SVGFELightElement; + +class SVGFEPointLightElement final : public SVGFEPointLightElementBase { + friend nsresult(::NS_NewSVGFEPointLightElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFEPointLightElement( + already_AddRefed&& aNodeInfo) + : SVGFEPointLightElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + + public: + mozilla::gfx::LightType ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray& aFloatAttributes) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed X(); + already_AddRefed Y(); + already_AddRefed Z(); + + protected: + NumberAttributesInfo GetNumberInfo() override; + + enum { ATTR_X, ATTR_Y, ATTR_Z }; + SVGAnimatedNumber mNumberAttributes[3]; + static NumberInfo sNumberInfo[3]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFEPOINTLIGHTELEMENT_H_ diff --git a/dom/svg/SVGFESpecularLightingElement.cpp b/dom/svg/SVGFESpecularLightingElement.cpp new file mode 100644 index 0000000000..ab1559ae68 --- /dev/null +++ b/dom/svg/SVGFESpecularLightingElement.cpp @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFESpecularLightingElement.h" +#include "mozilla/dom/SVGFESpecularLightingElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FESpecularLighting) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFESpecularLightingElement::WrapNode( + JSContext* aCx, JS::Handle aGivenProto) { + return SVGFESpecularLightingElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFESpecularLightingElement) + +already_AddRefed SVGFESpecularLightingElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +already_AddRefed +SVGFESpecularLightingElement::SurfaceScale() { + return mNumberAttributes[SURFACE_SCALE].ToDOMAnimatedNumber(this); +} + +already_AddRefed +SVGFESpecularLightingElement::SpecularConstant() { + return mNumberAttributes[SPECULAR_CONSTANT].ToDOMAnimatedNumber(this); +} + +already_AddRefed +SVGFESpecularLightingElement::SpecularExponent() { + return mNumberAttributes[SPECULAR_EXPONENT].ToDOMAnimatedNumber(this); +} + +already_AddRefed +SVGFESpecularLightingElement::KernelUnitLengthX() { + return mNumberPairAttributes[KERNEL_UNIT_LENGTH].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eFirst, this); +} + +already_AddRefed +SVGFESpecularLightingElement::KernelUnitLengthY() { + return mNumberPairAttributes[KERNEL_UNIT_LENGTH].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eSecond, this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +FilterPrimitiveDescription +SVGFESpecularLightingElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + float specularExponent = mNumberAttributes[SPECULAR_EXPONENT].GetAnimValue(); + float specularConstant = mNumberAttributes[SPECULAR_CONSTANT].GetAnimValue(); + + // specification defined range (15.22) + if (specularExponent < 1 || specularExponent > 128) { + return FilterPrimitiveDescription(); + } + + SpecularLightingAttributes atts; + atts.mLightingConstant = specularConstant; + atts.mSpecularExponent = specularExponent; + if (!AddLightingAttributes(static_cast(&atts), + aInstance)) { + return FilterPrimitiveDescription(); + } + + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFESpecularLightingElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFESpecularLightingElementBase::AttributeAffectsRendering( + aNameSpaceID, aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::specularConstant || + aAttribute == nsGkAtoms::specularExponent)); +} + +nsresult SVGFESpecularLightingElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feSpecularLighting); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFESpecularLightingElement.h b/dom/svg/SVGFESpecularLightingElement.h new file mode 100644 index 0000000000..c3911cf048 --- /dev/null +++ b/dom/svg/SVGFESpecularLightingElement.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFESPECULARLIGHTINGELEMENT_H_ +#define DOM_SVG_SVGFESPECULARLIGHTINGELEMENT_H_ + +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFESpecularLightingElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +//---------------------SpecularLighting------------------------ + +using SVGFESpecularLightingElementBase = SVGFELightingElement; + +class SVGFESpecularLightingElement final + : public SVGFESpecularLightingElementBase { + friend nsresult(::NS_NewSVGFESpecularLightingElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFESpecularLightingElement( + already_AddRefed&& aNodeInfo) + : SVGFESpecularLightingElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + + // WebIDL + already_AddRefed In1(); + already_AddRefed SurfaceScale(); + already_AddRefed SpecularConstant(); + already_AddRefed SpecularExponent(); + already_AddRefed KernelUnitLengthX(); + already_AddRefed KernelUnitLengthY(); +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFESPECULARLIGHTINGELEMENT_H_ diff --git a/dom/svg/SVGFESpotLightElement.cpp b/dom/svg/SVGFESpotLightElement.cpp new file mode 100644 index 0000000000..aa771952b3 --- /dev/null +++ b/dom/svg/SVGFESpotLightElement.cpp @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFESpotLightElement.h" +#include "mozilla/dom/SVGFESpotLightElementBinding.h" +#include "mozilla/SVGFilterInstance.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FESpotLight) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFESpotLightElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFESpotLightElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFESpotLightElement::sNumberInfo[8] = { + {nsGkAtoms::x, 0, false}, + {nsGkAtoms::y, 0, false}, + {nsGkAtoms::z, 0, false}, + {nsGkAtoms::pointsAtX, 0, false}, + {nsGkAtoms::pointsAtY, 0, false}, + {nsGkAtoms::pointsAtZ, 0, false}, + {nsGkAtoms::specularExponent, 1, false}, + {nsGkAtoms::limitingConeAngle, 0, false}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFESpotLightElement) + +//---------------------------------------------------------------------- +// nsFEUnstyledElement methods + +bool SVGFESpotLightElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y || + aAttribute == nsGkAtoms::z || aAttribute == nsGkAtoms::pointsAtX || + aAttribute == nsGkAtoms::pointsAtY || + aAttribute == nsGkAtoms::pointsAtZ || + aAttribute == nsGkAtoms::specularExponent || + aAttribute == nsGkAtoms::limitingConeAngle); +} + +//---------------------------------------------------------------------- + +LightType SVGFESpotLightElement::ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray& aFloatAttributes) { + aFloatAttributes.SetLength(kSpotLightNumAttributes); + GetAnimatedNumberValues(&aFloatAttributes[kSpotLightPositionXIndex], + &aFloatAttributes[kSpotLightPositionYIndex], + &aFloatAttributes[kSpotLightPositionZIndex], + &aFloatAttributes[kSpotLightPointsAtXIndex], + &aFloatAttributes[kSpotLightPointsAtYIndex], + &aFloatAttributes[kSpotLightPointsAtZIndex], + &aFloatAttributes[kSpotLightFocusIndex], + &aFloatAttributes[kSpotLightLimitingConeAngleIndex], + nullptr); + if (!mNumberAttributes[SVGFESpotLightElement::LIMITING_CONE_ANGLE] + .IsExplicitlySet()) { + aFloatAttributes[kSpotLightLimitingConeAngleIndex] = 90; + } + + return LightType::Spot; +} + +already_AddRefed SVGFESpotLightElement::X() { + return mNumberAttributes[ATTR_X].ToDOMAnimatedNumber(this); +} + +already_AddRefed SVGFESpotLightElement::Y() { + return mNumberAttributes[ATTR_Y].ToDOMAnimatedNumber(this); +} + +already_AddRefed SVGFESpotLightElement::Z() { + return mNumberAttributes[ATTR_Z].ToDOMAnimatedNumber(this); +} + +already_AddRefed SVGFESpotLightElement::PointsAtX() { + return mNumberAttributes[POINTS_AT_X].ToDOMAnimatedNumber(this); +} + +already_AddRefed SVGFESpotLightElement::PointsAtY() { + return mNumberAttributes[POINTS_AT_Y].ToDOMAnimatedNumber(this); +} + +already_AddRefed SVGFESpotLightElement::PointsAtZ() { + return mNumberAttributes[POINTS_AT_Z].ToDOMAnimatedNumber(this); +} + +already_AddRefed +SVGFESpotLightElement::SpecularExponent() { + return mNumberAttributes[SPECULAR_EXPONENT].ToDOMAnimatedNumber(this); +} + +already_AddRefed +SVGFESpotLightElement::LimitingConeAngle() { + return mNumberAttributes[LIMITING_CONE_ANGLE].ToDOMAnimatedNumber(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFESpotLightElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFESpotLightElement.h b/dom/svg/SVGFESpotLightElement.h new file mode 100644 index 0000000000..3429b42bc7 --- /dev/null +++ b/dom/svg/SVGFESpotLightElement.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFESPOTLIGHTELEMENT_H_ +#define DOM_SVG_SVGFESPOTLIGHTELEMENT_H_ + +#include "SVGAnimatedNumber.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFESpotLightElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFESpotLightElementBase = SVGFELightElement; + +class SVGFESpotLightElement final : public SVGFESpotLightElementBase { + friend nsresult(::NS_NewSVGFESpotLightElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + friend class SVGFELightingElement; + + protected: + explicit SVGFESpotLightElement( + already_AddRefed&& aNodeInfo) + : SVGFESpotLightElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + mozilla::gfx::LightType ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray& aFloatAttributes) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed X(); + already_AddRefed Y(); + already_AddRefed Z(); + already_AddRefed PointsAtX(); + already_AddRefed PointsAtY(); + already_AddRefed PointsAtZ(); + already_AddRefed SpecularExponent(); + already_AddRefed LimitingConeAngle(); + + protected: + NumberAttributesInfo GetNumberInfo() override; + + enum { + ATTR_X, + ATTR_Y, + ATTR_Z, + POINTS_AT_X, + POINTS_AT_Y, + POINTS_AT_Z, + SPECULAR_EXPONENT, + LIMITING_CONE_ANGLE + }; + SVGAnimatedNumber mNumberAttributes[8]; + static NumberInfo sNumberInfo[8]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFESPOTLIGHTELEMENT_H_ diff --git a/dom/svg/SVGFETileElement.cpp b/dom/svg/SVGFETileElement.cpp new file mode 100644 index 0000000000..51781ec7d2 --- /dev/null +++ b/dom/svg/SVGFETileElement.cpp @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFETileElement.h" +#include "mozilla/dom/SVGFETileElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FETile) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGFETileElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFETileElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGFETileElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFETileElement) + +already_AddRefed SVGFETileElement::In1() { + return mStringAttributes[IN1].ToDOMAnimatedString(this); +} + +void SVGFETileElement::GetSourceImageNames(nsTArray& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +FilterPrimitiveDescription SVGFETileElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + return FilterPrimitiveDescription(AsVariant(TileAttributes())); +} + +bool SVGFETileElement::AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const { + return SVGFETileElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::in); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::StringAttributesInfo SVGFETileElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +nsresult SVGFETileElement::BindToTree(BindContext& aCtx, nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feTile); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFETileElement.h b/dom/svg/SVGFETileElement.h new file mode 100644 index 0000000000..9300ee0e4a --- /dev/null +++ b/dom/svg/SVGFETileElement.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFETILEELEMENT_H_ +#define DOM_SVG_SVGFETILEELEMENT_H_ + +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFETileElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFETileElementBase = SVGFE; + +class SVGFETileElement final : public SVGFETileElementBase { + friend nsresult(::NS_NewSVGFETileElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFETileElement( + already_AddRefed&& aNodeInfo) + : SVGFETileElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + + public: + bool SubregionIsUnionOfRegions() override { return false; } + + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray& aSources) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext& aCtx, nsINode& aParent) override; + + // WebIDL + already_AddRefed In1(); + + protected: + StringAttributesInfo GetStringInfo() override; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFETILEELEMENT_H_ diff --git a/dom/svg/SVGFETurbulenceElement.cpp b/dom/svg/SVGFETurbulenceElement.cpp new file mode 100644 index 0000000000..bd92766c44 --- /dev/null +++ b/dom/svg/SVGFETurbulenceElement.cpp @@ -0,0 +1,194 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFETurbulenceElement.h" +#include "mozilla/dom/SVGFETurbulenceElementBinding.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/BindContext.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(FETurbulence) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +// Stitch Options +static const unsigned short SVG_STITCHTYPE_STITCH = 1; +static const unsigned short SVG_STITCHTYPE_NOSTITCH = 2; + +static const int32_t MAX_OCTAVES = 10; + +JSObject* SVGFETurbulenceElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFETurbulenceElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGFETurbulenceElement::sNumberInfo[1] = { + {nsGkAtoms::seed, 0, false}}; + +SVGElement::NumberPairInfo SVGFETurbulenceElement::sNumberPairInfo[1] = { + {nsGkAtoms::baseFrequency, 0, 0}}; + +SVGElement::IntegerInfo SVGFETurbulenceElement::sIntegerInfo[1] = { + {nsGkAtoms::numOctaves, 1}}; + +SVGEnumMapping SVGFETurbulenceElement::sTypeMap[] = { + {nsGkAtoms::fractalNoise, SVG_TURBULENCE_TYPE_FRACTALNOISE}, + {nsGkAtoms::turbulence, SVG_TURBULENCE_TYPE_TURBULENCE}, + {nullptr, 0}}; + +SVGEnumMapping SVGFETurbulenceElement::sStitchTilesMap[] = { + {nsGkAtoms::stitch, SVG_STITCHTYPE_STITCH}, + {nsGkAtoms::noStitch, SVG_STITCHTYPE_NOSTITCH}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGFETurbulenceElement::sEnumInfo[2] = { + {nsGkAtoms::type, sTypeMap, SVG_TURBULENCE_TYPE_TURBULENCE}, + {nsGkAtoms::stitchTiles, sStitchTilesMap, SVG_STITCHTYPE_NOSTITCH}}; + +SVGElement::StringInfo SVGFETurbulenceElement::sStringInfo[1] = { + {nsGkAtoms::result, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFETurbulenceElement) + +//---------------------------------------------------------------------- + +already_AddRefed +SVGFETurbulenceElement::BaseFrequencyX() { + return mNumberPairAttributes[BASE_FREQ].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eFirst, this); +} + +already_AddRefed +SVGFETurbulenceElement::BaseFrequencyY() { + return mNumberPairAttributes[BASE_FREQ].ToDOMAnimatedNumber( + SVGAnimatedNumberPair::eSecond, this); +} + +already_AddRefed SVGFETurbulenceElement::NumOctaves() { + return mIntegerAttributes[OCTAVES].ToDOMAnimatedInteger(this); +} + +already_AddRefed SVGFETurbulenceElement::Seed() { + return mNumberAttributes[SEED].ToDOMAnimatedNumber(this); +} + +already_AddRefed +SVGFETurbulenceElement::StitchTiles() { + return mEnumAttributes[STITCHTILES].ToDOMAnimatedEnum(this); +} + +already_AddRefed SVGFETurbulenceElement::Type() { + return mEnumAttributes[TYPE].ToDOMAnimatedEnum(this); +} + +FilterPrimitiveDescription SVGFETurbulenceElement::GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) { + float fX = mNumberPairAttributes[BASE_FREQ].GetAnimValue( + SVGAnimatedNumberPair::eFirst); + float fY = mNumberPairAttributes[BASE_FREQ].GetAnimValue( + SVGAnimatedNumberPair::eSecond); + float seed = mNumberAttributes[OCTAVES].GetAnimValue(); + uint32_t octaves = + clamped(mIntegerAttributes[OCTAVES].GetAnimValue(), 0, MAX_OCTAVES); + uint32_t type = mEnumAttributes[TYPE].GetAnimValue(); + uint16_t stitch = mEnumAttributes[STITCHTILES].GetAnimValue(); + + if (fX == 0 && fY == 0) { + // A base frequency of zero results in transparent black for + // type="turbulence" and in 50% alpha 50% gray for type="fractalNoise". + if (type == SVG_TURBULENCE_TYPE_TURBULENCE) { + return FilterPrimitiveDescription(); + } + FloodAttributes atts; + atts.mColor = sRGBColor(0.5, 0.5, 0.5, 0.5); + return FilterPrimitiveDescription(AsVariant(std::move(atts))); + } + + // We interpret the base frequency as relative to user space units. In other + // words, we consider one turbulence base period to be 1 / fX user space + // units wide and 1 / fY user space units high. We do not scale the frequency + // depending on the filter primitive region. + // We now convert the frequency from user space to filter space. + // If a frequency in user space units is zero, then it will also be zero in + // filter space. During the conversion we use a dummy period length of 1 + // for those frequencies but then ignore the converted length and use 0 + // for the converted frequency. This avoids division by zero. + gfxRect firstPeriodInUserSpace(0, 0, fX == 0 ? 1 : (1 / fX), + fY == 0 ? 1 : (1 / fY)); + gfxRect firstPeriodInFilterSpace = + aInstance->UserSpaceToFilterSpace(firstPeriodInUserSpace); + Size frequencyInFilterSpace( + fX == 0 ? 0 : (1 / firstPeriodInFilterSpace.width), + fY == 0 ? 0 : (1 / firstPeriodInFilterSpace.height)); + gfxPoint offset = firstPeriodInFilterSpace.TopLeft(); + + TurbulenceAttributes atts; + atts.mOffset = IntPoint::Truncate(offset.x, offset.y); + atts.mBaseFrequency = frequencyInFilterSpace; + atts.mSeed = seed; + atts.mOctaves = octaves; + atts.mStitchable = stitch == SVG_STITCHTYPE_STITCH; + atts.mType = type; + return FilterPrimitiveDescription(AsVariant(std::move(atts))); +} + +bool SVGFETurbulenceElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return SVGFETurbulenceElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::seed || + aAttribute == nsGkAtoms::baseFrequency || + aAttribute == nsGkAtoms::numOctaves || + aAttribute == nsGkAtoms::type || + aAttribute == nsGkAtoms::stitchTiles)); +} + +nsresult SVGFETurbulenceElement::BindToTree(BindContext& aCtx, + nsINode& aParent) { + if (aCtx.InComposedDoc()) { + aCtx.OwnerDoc().SetUseCounter(eUseCounter_custom_feTurbulence); + } + + return SVGFE::BindToTree(aCtx, aParent); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFETurbulenceElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +SVGElement::NumberPairAttributesInfo +SVGFETurbulenceElement::GetNumberPairInfo() { + return NumberPairAttributesInfo(mNumberPairAttributes, sNumberPairInfo, + ArrayLength(sNumberPairInfo)); +} + +SVGElement::IntegerAttributesInfo SVGFETurbulenceElement::GetIntegerInfo() { + return IntegerAttributesInfo(mIntegerAttributes, sIntegerInfo, + ArrayLength(sIntegerInfo)); +} + +SVGElement::EnumAttributesInfo SVGFETurbulenceElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGFETurbulenceElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFETurbulenceElement.h b/dom/svg/SVGFETurbulenceElement.h new file mode 100644 index 0000000000..2b7ab2c042 --- /dev/null +++ b/dom/svg/SVGFETurbulenceElement.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFETURBULENCEELEMENT_H_ +#define DOM_SVG_SVGFETURBULENCEELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedInteger.h" +#include "SVGAnimatedNumber.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGFilters.h" + +nsresult NS_NewSVGFETurbulenceElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGFETurbulenceElementBase = SVGFE; + +class SVGFETurbulenceElement final : public SVGFETurbulenceElementBase { + friend nsresult(::NS_NewSVGFETurbulenceElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGFETurbulenceElement( + already_AddRefed&& aNodeInfo) + : SVGFETurbulenceElementBase(std::move(aNodeInfo)) {} + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + bool SubregionIsUnionOfRegions() override { return false; } + + FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) override; + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + + // WebIDL + already_AddRefed BaseFrequencyX(); + already_AddRefed BaseFrequencyY(); + already_AddRefed NumOctaves(); + already_AddRefed Seed(); + already_AddRefed StitchTiles(); + already_AddRefed Type(); + + protected: + NumberAttributesInfo GetNumberInfo() override; + NumberPairAttributesInfo GetNumberPairInfo() override; + IntegerAttributesInfo GetIntegerInfo() override; + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { SEED }; // floating point seed?! + SVGAnimatedNumber mNumberAttributes[1]; + static NumberInfo sNumberInfo[1]; + + enum { BASE_FREQ }; + SVGAnimatedNumberPair mNumberPairAttributes[1]; + static NumberPairInfo sNumberPairInfo[1]; + + enum { OCTAVES }; + SVGAnimatedInteger mIntegerAttributes[1]; + static IntegerInfo sIntegerInfo[1]; + + enum { TYPE, STITCHTILES }; + SVGAnimatedEnumeration mEnumAttributes[2]; + static SVGEnumMapping sTypeMap[]; + static SVGEnumMapping sStitchTilesMap[]; + static EnumInfo sEnumInfo[2]; + + enum { RESULT }; + SVGAnimatedString mStringAttributes[1]; + static StringInfo sStringInfo[1]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGFETURBULENCEELEMENT_H_ diff --git a/dom/svg/SVGFilterElement.cpp b/dom/svg/SVGFilterElement.cpp new file mode 100644 index 0000000000..8140aa2164 --- /dev/null +++ b/dom/svg/SVGFilterElement.cpp @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGFilterElement.h" + +#include "nsGkAtoms.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGFilterElementBinding.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" +#include "nsQueryObject.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Filter) + +namespace mozilla::dom { + +using namespace SVGUnitTypes_Binding; + +JSObject* SVGFilterElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFilterElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGFilterElement::sLengthInfo[4] = { + {nsGkAtoms::x, -10, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::y, -10, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, + {nsGkAtoms::width, 120, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::height, 120, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, +}; + +SVGElement::EnumInfo SVGFilterElement::sEnumInfo[2] = { + {nsGkAtoms::filterUnits, sSVGUnitTypesMap, SVG_UNIT_TYPE_OBJECTBOUNDINGBOX}, + {nsGkAtoms::primitiveUnits, sSVGUnitTypesMap, + SVG_UNIT_TYPE_USERSPACEONUSE}}; + +SVGElement::StringInfo SVGFilterElement::sStringInfo[2] = { + {nsGkAtoms::href, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_XLink, true}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGFilterElement::SVGFilterElement( + already_AddRefed&& aNodeInfo) + : SVGFilterElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFilterElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGFilterElement::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGFilterElement::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGFilterElement::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGFilterElement::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGFilterElement::FilterUnits() { + return mEnumAttributes[FILTERUNITS].ToDOMAnimatedEnum(this); +} + +already_AddRefed SVGFilterElement::PrimitiveUnits() { + return mEnumAttributes[PRIMITIVEUNITS].ToDOMAnimatedEnum(this); +} + +already_AddRefed SVGFilterElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +bool SVGFilterElement::HasValidDimensions() const { + return (!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() || + mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) && + (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() || + mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0); +} + +SVGElement::LengthAttributesInfo SVGFilterElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +SVGElement::EnumAttributesInfo SVGFilterElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGFilterElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFilterElement.h b/dom/svg/SVGFilterElement.h new file mode 100644 index 0000000000..8ee3696a94 --- /dev/null +++ b/dom/svg/SVGFilterElement.h @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFILTERELEMENT_H_ +#define DOM_SVG_SVGFILTERELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGElement.h" + +nsresult NS_NewSVGFilterElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla { +class SVGFilterFrame; +class SVGFilterInstance; + +namespace dom { +class DOMSVGAnimatedLength; + +using SVGFilterElementBase = SVGElement; + +class SVGFilterElement final : public SVGFilterElementBase { + friend class mozilla::SVGFilterFrame; + friend class mozilla::SVGFilterInstance; + + protected: + friend nsresult(::NS_NewSVGFilterElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGFilterElement( + already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + + public: + NS_IMPL_FROMNODE_WITH_TAG(SVGFilterElement, kNameSpaceID_SVG, filter) + + // nsIContent + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + // WebIDL + already_AddRefed X(); + already_AddRefed Y(); + already_AddRefed Width(); + already_AddRefed Height(); + already_AddRefed FilterUnits(); + already_AddRefed PrimitiveUnits(); + already_AddRefed Href(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; + + enum { FILTERUNITS, PRIMITIVEUNITS }; + SVGAnimatedEnumeration mEnumAttributes[2]; + static EnumInfo sEnumInfo[2]; + + enum { HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGFILTERELEMENT_H_ diff --git a/dom/svg/SVGFilters.cpp b/dom/svg/SVGFilters.cpp new file mode 100644 index 0000000000..ff84375133 --- /dev/null +++ b/dom/svg/SVGFilters.cpp @@ -0,0 +1,450 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGFilters.h" + +#include +#include "DOMSVGAnimatedNumberList.h" +#include "DOMSVGAnimatedLength.h" +#include "nsGkAtoms.h" +#include "nsCOMPtr.h" +#include "nsIFrame.h" +#include "nsLayoutUtils.h" +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedNumberPair.h" +#include "SVGAnimatedString.h" +#include "SVGNumberList.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/SVGFilterInstance.h" +#include "mozilla/dom/SVGComponentTransferFunctionElement.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGFEDistantLightElement.h" +#include "mozilla/dom/SVGFEFuncAElementBinding.h" +#include "mozilla/dom/SVGFEFuncBElementBinding.h" +#include "mozilla/dom/SVGFEFuncGElementBinding.h" +#include "mozilla/dom/SVGFEFuncRElementBinding.h" +#include "mozilla/dom/SVGFEPointLightElement.h" +#include "mozilla/dom/SVGFESpotLightElement.h" +#include "mozilla/dom/SVGFilterElement.h" +#include "mozilla/dom/SVGLengthBinding.h" + +#if defined(XP_WIN) +// Prevent Windows redefining LoadImage +# undef LoadImage +#endif + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +//--------------------Filter Element Base Class----------------------- + +SVGElement::LengthInfo SVGFE::sLengthInfo[4] = { + {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, + {nsGkAtoms::width, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::height, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}}; + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ADDREF_INHERITED(SVGFE, SVGFEBase) +NS_IMPL_RELEASE_INHERITED(SVGFE, SVGFEBase) + +NS_INTERFACE_MAP_BEGIN(SVGFE) + NS_INTERFACE_MAP_ENTRY_CONCRETE(SVGFE) +NS_INTERFACE_MAP_END_INHERITING(SVGFEBase) + +//---------------------------------------------------------------------- +// Implementation + +void SVGFE::GetSourceImageNames(nsTArray& aSources) {} + +bool SVGFE::OutputIsTainted(const nsTArray& aInputsAreTainted, + nsIPrincipal* aReferencePrincipal) { + // This is the default implementation for OutputIsTainted. + // Our output is tainted if we have at least one tainted input. + for (uint32_t i = 0; i < aInputsAreTainted.Length(); i++) { + if (aInputsAreTainted[i]) { + return true; + } + } + return false; +} + +bool SVGFE::AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const { + return aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y || + aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height || + aAttribute == nsGkAtoms::result); +} + +already_AddRefed SVGFE::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGFE::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGFE::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGFE::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGFE::Result() { + return GetResultImageName().ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +bool SVGFE::StyleIsSetToSRGB() { + nsIFrame* frame = GetPrimaryFrame(); + if (!frame) return false; + + ComputedStyle* style = frame->Style(); + return style->StyleSVG()->mColorInterpolationFilters == + StyleColorInterpolation::Srgb; +} + +/* virtual */ +bool SVGFE::HasValidDimensions() const { + return (!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() || + mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) && + (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() || + mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0); +} + +Size SVGFE::GetKernelUnitLength(SVGFilterInstance* aInstance, + SVGAnimatedNumberPair* aKernelUnitLength) { + if (!aKernelUnitLength->IsExplicitlySet()) { + return Size(1, 1); + } + + float kernelX = aInstance->GetPrimitiveNumber( + SVGContentUtils::X, aKernelUnitLength, SVGAnimatedNumberPair::eFirst); + float kernelY = aInstance->GetPrimitiveNumber( + SVGContentUtils::Y, aKernelUnitLength, SVGAnimatedNumberPair::eSecond); + return Size(kernelX, kernelY); +} + +SVGElement::LengthAttributesInfo SVGFE::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +SVGElement::NumberListInfo + SVGComponentTransferFunctionElement::sNumberListInfo[1] = { + {nsGkAtoms::tableValues}}; + +SVGElement::NumberInfo SVGComponentTransferFunctionElement::sNumberInfo[5] = { + {nsGkAtoms::slope, 1, false}, + {nsGkAtoms::intercept, 0, false}, + {nsGkAtoms::amplitude, 1, false}, + {nsGkAtoms::exponent, 1, false}, + {nsGkAtoms::offset, 0, false}}; + +SVGEnumMapping SVGComponentTransferFunctionElement::sTypeMap[] = { + {nsGkAtoms::identity, SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY}, + {nsGkAtoms::table, SVG_FECOMPONENTTRANSFER_TYPE_TABLE}, + {nsGkAtoms::discrete, SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE}, + {nsGkAtoms::linear, SVG_FECOMPONENTTRANSFER_TYPE_LINEAR}, + {nsGkAtoms::gamma, SVG_FECOMPONENTTRANSFER_TYPE_GAMMA}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGComponentTransferFunctionElement::sEnumInfo[1] = { + {nsGkAtoms::type, sTypeMap, SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY}}; + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ADDREF_INHERITED(SVGComponentTransferFunctionElement, + SVGComponentTransferFunctionElementBase) +NS_IMPL_RELEASE_INHERITED(SVGComponentTransferFunctionElement, + SVGComponentTransferFunctionElementBase) + +NS_INTERFACE_MAP_BEGIN(SVGComponentTransferFunctionElement) + NS_INTERFACE_MAP_ENTRY_CONCRETE(SVGComponentTransferFunctionElement) +NS_INTERFACE_MAP_END_INHERITING(SVGComponentTransferFunctionElementBase) + +//---------------------------------------------------------------------- +// nsFEUnstyledElement methods + +bool SVGComponentTransferFunctionElement::AttributeAffectsRendering( + int32_t aNameSpaceID, nsAtom* aAttribute) const { + return aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::tableValues || + aAttribute == nsGkAtoms::slope || + aAttribute == nsGkAtoms::intercept || + aAttribute == nsGkAtoms::amplitude || + aAttribute == nsGkAtoms::exponent || + aAttribute == nsGkAtoms::offset || aAttribute == nsGkAtoms::type); +} + +//---------------------------------------------------------------------- + +already_AddRefed +SVGComponentTransferFunctionElement::Type() { + return mEnumAttributes[TYPE].ToDOMAnimatedEnum(this); +} + +already_AddRefed +SVGComponentTransferFunctionElement::TableValues() { + return DOMSVGAnimatedNumberList::GetDOMWrapper( + &mNumberListAttributes[TABLEVALUES], this, TABLEVALUES); +} + +already_AddRefed +SVGComponentTransferFunctionElement::Slope() { + return mNumberAttributes[SLOPE].ToDOMAnimatedNumber(this); +} + +already_AddRefed +SVGComponentTransferFunctionElement::Intercept() { + return mNumberAttributes[INTERCEPT].ToDOMAnimatedNumber(this); +} + +already_AddRefed +SVGComponentTransferFunctionElement::Amplitude() { + return mNumberAttributes[AMPLITUDE].ToDOMAnimatedNumber(this); +} + +already_AddRefed +SVGComponentTransferFunctionElement::Exponent() { + return mNumberAttributes[EXPONENT].ToDOMAnimatedNumber(this); +} + +already_AddRefed +SVGComponentTransferFunctionElement::Offset() { + return mNumberAttributes[OFFSET].ToDOMAnimatedNumber(this); +} + +void SVGComponentTransferFunctionElement::ComputeAttributes( + int32_t aChannel, ComponentTransferAttributes& aAttributes) { + uint32_t type = mEnumAttributes[TYPE].GetAnimValue(); + + float slope, intercept, amplitude, exponent, offset; + GetAnimatedNumberValues(&slope, &intercept, &litude, &exponent, &offset, + nullptr); + + const SVGNumberList& tableValues = + mNumberListAttributes[TABLEVALUES].GetAnimValue(); + + aAttributes.mTypes[aChannel] = (uint8_t)type; + switch (type) { + case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR: { + aAttributes.mValues[aChannel].SetLength(2); + aAttributes.mValues[aChannel][kComponentTransferSlopeIndex] = slope; + aAttributes.mValues[aChannel][kComponentTransferInterceptIndex] = + intercept; + break; + } + case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA: { + aAttributes.mValues[aChannel].SetLength(3); + aAttributes.mValues[aChannel][kComponentTransferAmplitudeIndex] = + amplitude; + aAttributes.mValues[aChannel][kComponentTransferExponentIndex] = exponent; + aAttributes.mValues[aChannel][kComponentTransferOffsetIndex] = offset; + break; + } + case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE: + case SVG_FECOMPONENTTRANSFER_TYPE_TABLE: { + if (!tableValues.IsEmpty()) { + aAttributes.mValues[aChannel].AppendElements(&tableValues[0], + tableValues.Length()); + } + break; + } + } +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberListAttributesInfo +SVGComponentTransferFunctionElement::GetNumberListInfo() { + return NumberListAttributesInfo(mNumberListAttributes, sNumberListInfo, + ArrayLength(sNumberListInfo)); +} + +SVGElement::EnumAttributesInfo +SVGComponentTransferFunctionElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::NumberAttributesInfo +SVGComponentTransferFunctionElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +/* virtual */ +JSObject* SVGFEFuncRElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFEFuncRElement_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEFuncR) + +namespace mozilla::dom { + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEFuncRElement) + +/* virtual */ +JSObject* SVGFEFuncGElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFEFuncGElement_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEFuncG) + +namespace mozilla::dom { + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEFuncGElement) + +/* virtual */ +JSObject* SVGFEFuncBElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFEFuncBElement_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEFuncB) + +namespace mozilla::dom { + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEFuncBElement) + +/* virtual */ +JSObject* SVGFEFuncAElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGFEFuncAElement_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom + +NS_IMPL_NS_NEW_SVG_ELEMENT(FEFuncA) + +namespace mozilla::dom { + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGFEFuncAElement) + +//-------------------------------------------------------------------- +// +SVGElement::NumberInfo SVGFELightingElement::sNumberInfo[4] = { + {nsGkAtoms::surfaceScale, 1, false}, + {nsGkAtoms::diffuseConstant, 1, false}, + {nsGkAtoms::specularConstant, 1, false}, + {nsGkAtoms::specularExponent, 1, false}}; + +SVGElement::NumberPairInfo SVGFELightingElement::sNumberPairInfo[1] = { + {nsGkAtoms::kernelUnitLength, 0, 0}}; + +SVGElement::StringInfo SVGFELightingElement::sStringInfo[2] = { + {nsGkAtoms::result, kNameSpaceID_None, true}, + {nsGkAtoms::in, kNameSpaceID_None, true}}; + +//---------------------------------------------------------------------- +// Implementation + +void SVGFELightingElement::GetSourceImageNames( + nsTArray& aSources) { + aSources.AppendElement(SVGStringInfo(&mStringAttributes[IN1], this)); +} + +LightType SVGFELightingElement::ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray& aFloatAttributes) { + // find specified light + for (nsCOMPtr child = nsINode::GetFirstChild(); child; + child = child->GetNextSibling()) { + if (child->IsAnyOfSVGElements(nsGkAtoms::feDistantLight, + nsGkAtoms::fePointLight, + nsGkAtoms::feSpotLight)) { + return static_cast(child.get()) + ->ComputeLightAttributes(aInstance, aFloatAttributes); + } + } + + return LightType::None; +} + +bool SVGFELightingElement::AddLightingAttributes( + mozilla::gfx::DiffuseLightingAttributes* aAttributes, + SVGFilterInstance* aInstance) { + nsIFrame* frame = GetPrimaryFrame(); + if (!frame) { + return false; + } + + const nsStyleSVGReset* styleSVGReset = frame->Style()->StyleSVGReset(); + sRGBColor color( + sRGBColor::FromABGR(styleSVGReset->mLightingColor.CalcColor(frame))); + color.a = 1.f; + float surfaceScale = mNumberAttributes[SURFACE_SCALE].GetAnimValue(); + Size kernelUnitLength = GetKernelUnitLength( + aInstance, &mNumberPairAttributes[KERNEL_UNIT_LENGTH]); + + if (kernelUnitLength.width <= 0 || kernelUnitLength.height <= 0) { + // According to spec, A negative or zero value is an error. See link below + // for details. + // https://www.w3.org/TR/SVG/filters.html#feSpecularLightingKernelUnitLengthAttribute + return false; + } + + aAttributes->mLightType = + ComputeLightAttributes(aInstance, aAttributes->mLightValues); + aAttributes->mSurfaceScale = surfaceScale; + aAttributes->mKernelUnitLength = kernelUnitLength; + aAttributes->mColor = color; + + return true; +} + +bool SVGFELightingElement::AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const { + return SVGFELightingElementBase::AttributeAffectsRendering(aNameSpaceID, + aAttribute) || + (aNameSpaceID == kNameSpaceID_None && + (aAttribute == nsGkAtoms::in || + aAttribute == nsGkAtoms::surfaceScale || + aAttribute == nsGkAtoms::kernelUnitLength)); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::NumberAttributesInfo SVGFELightingElement::GetNumberInfo() { + return NumberAttributesInfo(mNumberAttributes, sNumberInfo, + ArrayLength(sNumberInfo)); +} + +SVGElement::NumberPairAttributesInfo SVGFELightingElement::GetNumberPairInfo() { + return NumberPairAttributesInfo(mNumberPairAttributes, sNumberPairInfo, + ArrayLength(sNumberPairInfo)); +} + +SVGElement::StringAttributesInfo SVGFELightingElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGFilters.h b/dom/svg/SVGFilters.h new file mode 100644 index 0000000000..5ecec2a445 --- /dev/null +++ b/dom/svg/SVGFilters.h @@ -0,0 +1,243 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFILTERS_H_ +#define DOM_SVG_SVGFILTERS_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/dom/SVGElement.h" +#include "FilterDescription.h" +#include "nsImageLoadingContent.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedNumber.h" +#include "SVGAnimatedNumberPair.h" +#include "SVGAnimatedString.h" + +namespace mozilla { +class SVGFilterInstance; + +namespace dom { + +struct SVGStringInfo { + SVGStringInfo(const SVGAnimatedString* aString, SVGElement* aElement) + : mString(aString), mElement(aElement) {} + + const SVGAnimatedString* mString; + SVGElement* mElement; +}; + +using SVGFEBase = SVGElement; + +#define NS_SVG_FE_CID \ + { \ + 0x60483958, 0xd229, 0x4a77, { \ + 0x96, 0xb2, 0x62, 0x3e, 0x69, 0x95, 0x1e, 0x0e \ + } \ + } + +/** + * Base class for filter primitive elements + * Children of those elements e.g. feMergeNode + * derive from SVGFEUnstyledElement instead + */ +class SVGFE : public SVGFEBase { + friend class mozilla::SVGFilterInstance; + + protected: + using SourceSurface = mozilla::gfx::SourceSurface; + using Size = mozilla::gfx::Size; + using IntRect = mozilla::gfx::IntRect; + using ColorSpace = mozilla::gfx::ColorSpace; + using FilterPrimitiveDescription = mozilla::gfx::FilterPrimitiveDescription; + + explicit SVGFE(already_AddRefed&& aNodeInfo) + : SVGFEBase(std::move(aNodeInfo)) {} + virtual ~SVGFE() = default; + + public: + using PrimitiveAttributes = mozilla::gfx::PrimitiveAttributes; + + ColorSpace GetInputColorSpace(int32_t aInputIndex, + ColorSpace aUnchangedInputColorSpace) { + return OperatesOnSRGB(aInputIndex, + aUnchangedInputColorSpace == ColorSpace::SRGB) + ? ColorSpace::SRGB + : ColorSpace::LinearRGB; + } + + // This is only called for filter primitives without inputs. For primitives + // with inputs, the output color model is the same as of the first input. + ColorSpace GetOutputColorSpace() { + return ProducesSRGB() ? ColorSpace::SRGB : ColorSpace::LinearRGB; + } + + // See http://www.w3.org/TR/SVG/filters.html#FilterPrimitiveSubRegion + virtual bool SubregionIsUnionOfRegions() { return true; } + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_SVG_FE_CID) + + // interfaces: + NS_DECL_ISUPPORTS_INHERITED + + // SVGElement interface + nsresult Clone(mozilla::dom::NodeInfo*, nsINode** aResult) const override = 0; + + bool HasValidDimensions() const override; + + virtual SVGAnimatedString& GetResultImageName() = 0; + // Return a list of all image names used as sources. Default is to + // return no sources. + virtual void GetSourceImageNames(nsTArray& aSources); + + virtual FilterPrimitiveDescription GetPrimitiveDescription( + SVGFilterInstance* aInstance, const IntRect& aFilterSubregion, + const nsTArray& aInputsAreTainted, + nsTArray>& aInputImages) = 0; + + // returns true if changes to the attribute should cause us to + // repaint the filter + virtual bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const; + + // Return whether this filter primitive has tainted output. A filter's + // output is tainted if it depends on things that the web page is not + // allowed to read from, e.g. the source graphic or cross-origin images. + // aReferencePrincipal is the node principal of the filtered frame's element. + virtual bool OutputIsTainted(const nsTArray& aInputsAreTainted, + nsIPrincipal* aReferencePrincipal); + + static nsIntRect GetMaxRect() { + // Try to avoid overflow errors dealing with this rect. It will + // be intersected with some other reasonable-sized rect eventually. + return nsIntRect(INT32_MIN / 2, INT32_MIN / 2, INT32_MAX, INT32_MAX); + } + + operator nsISupports*() { return static_cast(this); } + + // WebIDL + already_AddRefed X(); + already_AddRefed Y(); + already_AddRefed Width(); + already_AddRefed Height(); + already_AddRefed Result(); + + protected: + virtual bool OperatesOnSRGB(int32_t aInputIndex, bool aInputIsAlreadySRGB) { + return StyleIsSetToSRGB(); + } + + // Only called for filter primitives without inputs. + virtual bool ProducesSRGB() { return StyleIsSetToSRGB(); } + + bool StyleIsSetToSRGB(); + + // SVGElement specializations: + LengthAttributesInfo GetLengthInfo() override; + + Size GetKernelUnitLength(SVGFilterInstance* aInstance, + SVGAnimatedNumberPair* aKernelUnitLength); + + enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(SVGFE, NS_SVG_FE_CID) + +using SVGFEUnstyledElementBase = SVGElement; + +class SVGFEUnstyledElement : public SVGFEUnstyledElementBase { + protected: + explicit SVGFEUnstyledElement( + already_AddRefed&& aNodeInfo) + : SVGFEUnstyledElementBase(std::move(aNodeInfo)) {} + + public: + nsresult Clone(mozilla::dom::NodeInfo*, nsINode** aResult) const override = 0; + + // returns true if changes to the attribute should cause us to + // repaint the filter + virtual bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const = 0; +}; + +//------------------------------------------------------------ + +using SVGFELightingElementBase = SVGFE; + +class SVGFELightingElement : public SVGFELightingElementBase { + protected: + explicit SVGFELightingElement( + already_AddRefed&& aNodeInfo) + : SVGFELightingElementBase(std::move(aNodeInfo)) {} + + virtual ~SVGFELightingElement() = default; + + public: + // interfaces: + NS_INLINE_DECL_REFCOUNTING_INHERITED(SVGFELightingElement, + SVGFELightingElementBase) + + bool AttributeAffectsRendering(int32_t aNameSpaceID, + nsAtom* aAttribute) const override; + SVGAnimatedString& GetResultImageName() override { + return mStringAttributes[RESULT]; + } + void GetSourceImageNames(nsTArray& aSources) override; + + protected: + bool OperatesOnSRGB(int32_t aInputIndex, bool aInputIsAlreadySRGB) override { + return true; + } + + NumberAttributesInfo GetNumberInfo() override; + NumberPairAttributesInfo GetNumberPairInfo() override; + StringAttributesInfo GetStringInfo() override; + + mozilla::gfx::LightType ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray& aFloatAttributes); + + bool AddLightingAttributes( + mozilla::gfx::DiffuseLightingAttributes* aAttributes, + SVGFilterInstance* aInstance); + + enum { + SURFACE_SCALE, + DIFFUSE_CONSTANT, + SPECULAR_CONSTANT, + SPECULAR_EXPONENT + }; + SVGAnimatedNumber mNumberAttributes[4]; + static NumberInfo sNumberInfo[4]; + + enum { KERNEL_UNIT_LENGTH }; + SVGAnimatedNumberPair mNumberPairAttributes[1]; + static NumberPairInfo sNumberPairInfo[1]; + + enum { RESULT, IN1 }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +using SVGFELightElementBase = SVGFEUnstyledElement; + +class SVGFELightElement : public SVGFELightElementBase { + protected: + explicit SVGFELightElement( + already_AddRefed&& aNodeInfo) + : SVGFELightElementBase(std::move(aNodeInfo)) {} + + public: + using PrimitiveAttributes = gfx::PrimitiveAttributes; + + virtual mozilla::gfx::LightType ComputeLightAttributes( + SVGFilterInstance* aInstance, nsTArray& aFloatAttributes) = 0; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGFILTERS_H_ diff --git a/dom/svg/SVGForeignObjectElement.cpp b/dom/svg/SVGForeignObjectElement.cpp new file mode 100644 index 0000000000..d1d7c46f43 --- /dev/null +++ b/dom/svg/SVGForeignObjectElement.cpp @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGForeignObjectElement.h" + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGDocument.h" +#include "mozilla/dom/SVGForeignObjectElementBinding.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "SVGGeometryProperty.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(ForeignObject) + +namespace mozilla::dom { + +JSObject* SVGForeignObjectElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGForeignObjectElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGForeignObjectElement::sLengthInfo[4] = { + {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::width, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::height, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, +}; + +//---------------------------------------------------------------------- +// Implementation + +SVGForeignObjectElement::SVGForeignObjectElement( + already_AddRefed&& aNodeInfo) + : SVGGraphicsElement(std::move(aNodeInfo)) {} + +namespace SVGT = SVGGeometryProperty::Tags; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGForeignObjectElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGForeignObjectElement::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGForeignObjectElement::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGForeignObjectElement::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGForeignObjectElement::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +gfxMatrix SVGForeignObjectElement::PrependLocalTransformsTo( + const gfxMatrix& aMatrix, SVGTransformTypes aWhich) const { + // 'transform' attribute: + gfxMatrix fromUserSpace = + SVGGraphicsElement::PrependLocalTransformsTo(aMatrix, aWhich); + if (aWhich == eUserSpaceToParent) { + return fromUserSpace; + } + // our 'x' and 'y' attributes: + float x, y; + + if (!SVGGeometryProperty::ResolveAll(this, &x, &y)) { + // This function might be called for element in display:none subtree + // (e.g. getScreenCTM), we fall back to use SVG attributes. + const_cast(this)->GetAnimatedLengthValues( + &x, &y, nullptr); + } + + gfxMatrix toUserSpace = gfxMatrix::Translation(x, y); + if (aWhich == eChildToUserSpace) { + return toUserSpace * aMatrix; + } + MOZ_ASSERT(aWhich == eAllTransforms, "Unknown TransformTypes"); + return toUserSpace * fromUserSpace; +} + +/* virtual */ +bool SVGForeignObjectElement::HasValidDimensions() const { + float width, height; + + DebugOnly ok = + SVGGeometryProperty::ResolveAll( + const_cast(this), &width, &height); + MOZ_ASSERT(ok, "SVGGeometryProperty::ResolveAll failed"); + return width > 0 && height > 0; +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGForeignObjectElement::IsAttributeMapped(const nsAtom* name) const { + return IsInLengthInfo(name, sLengthInfo) || + SVGGraphicsElement::IsAttributeMapped(name); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::LengthAttributesInfo SVGForeignObjectElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +nsCSSPropertyID SVGForeignObjectElement::GetCSSPropertyIdForAttrEnum( + uint8_t aAttrEnum) { + switch (aAttrEnum) { + case ATTR_X: + return eCSSProperty_x; + case ATTR_Y: + return eCSSProperty_y; + case ATTR_WIDTH: + return eCSSProperty_width; + case ATTR_HEIGHT: + return eCSSProperty_height; + default: + MOZ_ASSERT_UNREACHABLE("Unknown attr enum"); + return eCSSProperty_UNKNOWN; + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGForeignObjectElement.h b/dom/svg/SVGForeignObjectElement.h new file mode 100644 index 0000000000..bd313a689d --- /dev/null +++ b/dom/svg/SVGForeignObjectElement.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFOREIGNOBJECTELEMENT_H_ +#define DOM_SVG_SVGFOREIGNOBJECTELEMENT_H_ + +#include "mozilla/dom/SVGGraphicsElement.h" +#include "nsCSSPropertyID.h" +#include "SVGAnimatedLength.h" + +nsresult NS_NewSVGForeignObjectElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla { +class SVGForeignObjectFrame; + +namespace dom { + +class SVGForeignObjectElement final : public SVGGraphicsElement { + friend class mozilla::SVGForeignObjectFrame; + + protected: + friend nsresult(::NS_NewSVGForeignObjectElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGForeignObjectElement( + already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + + public: + // SVGElement specializations: + virtual gfxMatrix PrependLocalTransformsTo( + const gfxMatrix& aMatrix, + SVGTransformTypes aWhich = eAllTransforms) const override; + bool HasValidDimensions() const override; + + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* name) const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + static nsCSSPropertyID GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum); + + // WebIDL + already_AddRefed X(); + already_AddRefed Y(); + already_AddRefed Width(); + already_AddRefed Height(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + + enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGFOREIGNOBJECTELEMENT_H_ diff --git a/dom/svg/SVGFragmentIdentifier.cpp b/dom/svg/SVGFragmentIdentifier.cpp new file mode 100644 index 0000000000..0932afd8ca --- /dev/null +++ b/dom/svg/SVGFragmentIdentifier.cpp @@ -0,0 +1,182 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGFragmentIdentifier.h" + +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/dom/SVGViewElement.h" +#include "mozilla/SVGOuterSVGFrame.h" +#include "nsCharSeparatedTokenizer.h" +#include "SVGAnimatedTransformList.h" + +namespace mozilla { + +using namespace dom; + +static bool IsMatchingParameter(const nsAString& aString, + const nsAString& aParameterName) { + // The first two tests ensure aString.Length() > aParameterName.Length() + // so it's then safe to do the third test + return StringBeginsWith(aString, aParameterName) && aString.Last() == ')' && + aString.CharAt(aParameterName.Length()) == '('; +} + +// Handles setting/clearing the root's mSVGView pointer. +class MOZ_RAII AutoSVGViewHandler { + public: + explicit AutoSVGViewHandler(SVGSVGElement* aRoot) + : mRoot(aRoot), mValid(false) { + mWasOverridden = mRoot->UseCurrentView(); + mRoot->mSVGView = nullptr; + mRoot->mCurrentViewID = nullptr; + } + + ~AutoSVGViewHandler() { + if (!mWasOverridden && !mValid) { + // we weren't overridden before and we aren't + // overridden now so nothing has changed. + return; + } + if (mValid) { + mRoot->mSVGView = std::move(mSVGView); + } + mRoot->InvalidateTransformNotifyFrame(); + if (nsIFrame* f = mRoot->GetPrimaryFrame()) { + if (SVGOuterSVGFrame* osf = do_QueryFrame(f)) { + osf->MaybeSendIntrinsicSizeAndRatioToEmbedder(); + } + } + } + + void CreateSVGView() { + MOZ_ASSERT(!mSVGView, "CreateSVGView should not be called multiple times"); + mSVGView = MakeUnique(); + } + + bool ProcessAttr(const nsAString& aToken, const nsAString& aParams) { + MOZ_ASSERT(mSVGView, "CreateSVGView should have been called"); + + // SVGViewAttributes may occur in any order, but each type may only occur + // at most one time in a correctly formed SVGViewSpec. + // If we encounter any attribute more than once or get any syntax errors + // we're going to return false and cancel any changes. + + if (IsMatchingParameter(aToken, u"viewBox"_ns)) { + if (mSVGView->mViewBox.IsExplicitlySet() || + NS_FAILED( + mSVGView->mViewBox.SetBaseValueString(aParams, mRoot, false))) { + return false; + } + } else if (IsMatchingParameter(aToken, u"preserveAspectRatio"_ns)) { + if (mSVGView->mPreserveAspectRatio.IsExplicitlySet() || + NS_FAILED(mSVGView->mPreserveAspectRatio.SetBaseValueString( + aParams, mRoot, false))) { + return false; + } + } else if (IsMatchingParameter(aToken, u"transform"_ns)) { + if (mSVGView->mTransforms) { + return false; + } + mSVGView->mTransforms = MakeUnique(); + if (NS_FAILED( + mSVGView->mTransforms->SetBaseValueString(aParams, mRoot))) { + return false; + } + } else if (IsMatchingParameter(aToken, u"zoomAndPan"_ns)) { + if (mSVGView->mZoomAndPan.IsExplicitlySet()) { + return false; + } + nsAtom* valAtom = NS_GetStaticAtom(aParams); + if (!valAtom || !mSVGView->mZoomAndPan.SetBaseValueAtom(valAtom, mRoot)) { + return false; + } + } else { + return false; + } + return true; + } + + void SetValid() { mValid = true; } + + private: + SVGSVGElement* mRoot; + UniquePtr mSVGView; + bool mValid; + bool mWasOverridden; +}; + +bool SVGFragmentIdentifier::ProcessSVGViewSpec(const nsAString& aViewSpec, + SVGSVGElement* aRoot) { + AutoSVGViewHandler viewHandler(aRoot); + + if (!IsMatchingParameter(aViewSpec, u"svgView"_ns)) { + return false; + } + + // Each token is a SVGViewAttribute + int32_t bracketPos = aViewSpec.FindChar('('); + uint32_t lengthOfViewSpec = aViewSpec.Length() - bracketPos - 2; + nsCharSeparatedTokenizerTemplate tokenizer( + Substring(aViewSpec, bracketPos + 1, lengthOfViewSpec), ';'); + + if (!tokenizer.hasMoreTokens()) { + return false; + } + viewHandler.CreateSVGView(); + + do { + nsAutoString token(tokenizer.nextToken()); + + bracketPos = token.FindChar('('); + if (bracketPos < 1 || token.Last() != ')') { + // invalid SVGViewAttribute syntax + return false; + } + + const nsAString& params = + Substring(token, bracketPos + 1, token.Length() - bracketPos - 2); + + if (!viewHandler.ProcessAttr(token, params)) { + return false; + } + + } while (tokenizer.hasMoreTokens()); + + viewHandler.SetValid(); + return true; +} + +bool SVGFragmentIdentifier::ProcessFragmentIdentifier( + Document* aDocument, const nsAString& aAnchorName) { + MOZ_ASSERT(aDocument->GetRootElement()->IsSVGElement(nsGkAtoms::svg), + "expecting an SVG root element"); + + auto* rootElement = SVGSVGElement::FromNode(aDocument->GetRootElement()); + + const auto* viewElement = + SVGViewElement::FromNodeOrNull(aDocument->GetElementById(aAnchorName)); + + if (viewElement) { + if (!rootElement->mCurrentViewID) { + rootElement->mCurrentViewID = MakeUnique(); + } + *rootElement->mCurrentViewID = aAnchorName; + rootElement->mSVGView = nullptr; + rootElement->InvalidateTransformNotifyFrame(); + if (nsIFrame* f = rootElement->GetPrimaryFrame()) { + if (SVGOuterSVGFrame* osf = do_QueryFrame(f)) { + osf->MaybeSendIntrinsicSizeAndRatioToEmbedder(); + } + } + // not an svgView()-style fragment identifier, return false so the caller + // continues processing to match any :target pseudo elements + return false; + } + + return ProcessSVGViewSpec(aAnchorName, rootElement); +} + +} // namespace mozilla diff --git a/dom/svg/SVGFragmentIdentifier.h b/dom/svg/SVGFragmentIdentifier.h new file mode 100644 index 0000000000..464412d1ed --- /dev/null +++ b/dom/svg/SVGFragmentIdentifier.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGFRAGMENTIDENTIFIER_H_ +#define DOM_SVG_SVGFRAGMENTIDENTIFIER_H_ + +#include "nsString.h" + +namespace mozilla { + +namespace dom { +class Document; +class SVGSVGElement; +} // namespace dom + +/** + * Implements support for parsing SVG fragment identifiers + * http://www.w3.org/TR/SVG/linking.html#SVGFragmentIdentifiers + */ +class SVGFragmentIdentifier { + // To prevent the class being instantiated + SVGFragmentIdentifier() = delete; + + public: + /** + * Process the SVG fragment identifier, if there is one. + * @return true if we found a valid svgView()-style fragment identifier, + * in which case further processing by the caller can stop. Otherwise return + * false as we may have an ordinary anchor which needs to be :target matched. + */ + static bool ProcessFragmentIdentifier(dom::Document* aDocument, + const nsAString& aAnchorName); + + private: + /** + * Parse an SVG ViewSpec and set applicable attributes on the root element. + * @return true if there is a valid ViewSpec + */ + static bool ProcessSVGViewSpec(const nsAString& aViewSpec, + dom::SVGSVGElement* root); +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGFRAGMENTIDENTIFIER_H_ diff --git a/dom/svg/SVGGElement.cpp b/dom/svg/SVGGElement.cpp new file mode 100644 index 0000000000..40d81fea84 --- /dev/null +++ b/dom/svg/SVGGElement.cpp @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGGElement.h" +#include "mozilla/dom/SVGGElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(G) + +namespace mozilla::dom { + +JSObject* SVGGElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGGElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGGElement::SVGGElement(already_AddRefed&& aNodeInfo) + : SVGGraphicsElement(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGGElement) + +} // namespace mozilla::dom diff --git a/dom/svg/SVGGElement.h b/dom/svg/SVGGElement.h new file mode 100644 index 0000000000..c22157c7c7 --- /dev/null +++ b/dom/svg/SVGGElement.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGGELEMENT_H_ +#define DOM_SVG_SVGGELEMENT_H_ + +#include "mozilla/dom/SVGGraphicsElement.h" + +nsresult NS_NewSVGGElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +class SVGGElement final : public SVGGraphicsElement { + protected: + explicit SVGGElement(already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + friend nsresult(::NS_NewSVGGElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + public: + // nsIContent + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGGELEMENT_H_ diff --git a/dom/svg/SVGGeometryElement.cpp b/dom/svg/SVGGeometryElement.cpp new file mode 100644 index 0000000000..726751c4ce --- /dev/null +++ b/dom/svg/SVGGeometryElement.cpp @@ -0,0 +1,300 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGGeometryElement.h" + +#include "DOMSVGPoint.h" +#include "gfxPlatform.h" +#include "nsCOMPtr.h" +#include "SVGAnimatedLength.h" +#include "SVGCircleElement.h" +#include "SVGEllipseElement.h" +#include "SVGGeometryProperty.h" +#include "SVGPathElement.h" +#include "SVGRectElement.h" +#include "mozilla/dom/DOMPointBinding.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/SVGContentUtils.h" + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +SVGElement::NumberInfo SVGGeometryElement::sNumberInfo = {nsGkAtoms::pathLength, + 0, false}; + +//---------------------------------------------------------------------- +// Implementation + +SVGGeometryElement::SVGGeometryElement( + already_AddRefed&& aNodeInfo) + : SVGGeometryElementBase(std::move(aNodeInfo)) {} + +SVGElement::NumberAttributesInfo SVGGeometryElement::GetNumberInfo() { + return NumberAttributesInfo(&mPathLength, &sNumberInfo, 1); +} + +void SVGGeometryElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, + bool aNotify) { + if (mCachedPath && aNamespaceID == kNameSpaceID_None && + AttributeDefinesGeometry(aName)) { + mCachedPath = nullptr; + } + return SVGGeometryElementBase::AfterSetAttr( + aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); +} + +bool SVGGeometryElement::AttributeDefinesGeometry(const nsAtom* aName) { + if (aName == nsGkAtoms::pathLength) { + return true; + } + + // Check for SVGAnimatedLength attribute + LengthAttributesInfo info = GetLengthInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return true; + } + } + + return false; +} + +bool SVGGeometryElement::GeometryDependsOnCoordCtx() { + // Check the SVGAnimatedLength attribute + LengthAttributesInfo info = + const_cast(this)->GetLengthInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (info.mValues[i].GetSpecifiedUnitType() == + SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE) { + return true; + } + } + return false; +} + +bool SVGGeometryElement::IsMarkable() { return false; } + +void SVGGeometryElement::GetMarkPoints(nsTArray* aMarks) {} + +already_AddRefed SVGGeometryElement::GetOrBuildPath( + const DrawTarget* aDrawTarget, FillRule aFillRule) { + // We only cache the path if it matches the backend used for screen painting, + // and it's not a capturing drawtarget. A capturing DT might start using the + // the Path object on a different thread (OMTP), and we might have a data race + // if we keep a handle to it. + bool cacheable = aDrawTarget->GetBackendType() == + gfxPlatform::GetPlatform()->GetDefaultContentBackend(); + + if (cacheable && mCachedPath && mCachedPath->GetFillRule() == aFillRule && + aDrawTarget->GetBackendType() == mCachedPath->GetBackendType()) { + RefPtr path(mCachedPath); + return path.forget(); + } + RefPtr builder = aDrawTarget->CreatePathBuilder(aFillRule); + RefPtr path = BuildPath(builder); + if (cacheable) { + mCachedPath = path; + } + return path.forget(); +} + +already_AddRefed SVGGeometryElement::GetOrBuildPathForMeasuring() { + RefPtr drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + FillRule fillRule = mCachedPath ? mCachedPath->GetFillRule() : GetFillRule(); + return GetOrBuildPath(drawTarget, fillRule); +} + +// This helper is currently identical to GetOrBuildPathForMeasuring. +// We keep it a separate method because for length measuring purpose, +// fillRule isn't really needed. Derived class (e.g. SVGPathElement) +// may override GetOrBuildPathForMeasuring() to ignore fillRule. And +// GetOrBuildPathForMeasuring() itself may be modified in the future. +already_AddRefed SVGGeometryElement::GetOrBuildPathForHitTest() { + RefPtr drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + FillRule fillRule = mCachedPath ? mCachedPath->GetFillRule() : GetFillRule(); + return GetOrBuildPath(drawTarget, fillRule); +} + +bool SVGGeometryElement::IsGeometryChangedViaCSS( + ComputedStyle const& aNewStyle, ComputedStyle const& aOldStyle) const { + nsAtom* name = NodeInfo()->NameAtom(); + if (name == nsGkAtoms::rect) { + return SVGRectElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle); + } + if (name == nsGkAtoms::circle) { + return SVGCircleElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle); + } + if (name == nsGkAtoms::ellipse) { + return SVGEllipseElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle); + } + if (name == nsGkAtoms::path) { + return SVGPathElement::IsDPropertyChangedViaCSS(aNewStyle, aOldStyle); + } + return false; +} + +FillRule SVGGeometryElement::GetFillRule() { + FillRule fillRule = + FillRule::FILL_WINDING; // Equivalent to StyleFillRule::Nonzero + + bool res = SVGGeometryProperty::DoForComputedStyle( + this, [&](const ComputedStyle* s) { + const auto* styleSVG = s->StyleSVG(); + + MOZ_ASSERT(styleSVG->mFillRule == StyleFillRule::Nonzero || + styleSVG->mFillRule == StyleFillRule::Evenodd); + + if (styleSVG->mFillRule == StyleFillRule::Evenodd) { + fillRule = FillRule::FILL_EVEN_ODD; + } + }); + + if (!res) { + NS_WARNING("Couldn't get ComputedStyle for content in GetFillRule"); + } + + return fillRule; +} + +static Point GetPointFrom(const DOMPointInit& aPoint) { + return Point(aPoint.mX, aPoint.mY); +} + +bool SVGGeometryElement::IsPointInFill(const DOMPointInit& aPoint) { + // d is a presentation attribute, so make sure style is up to date: + FlushStyleIfNeeded(); + + RefPtr path = GetOrBuildPathForHitTest(); + if (!path) { + return false; + } + + auto point = GetPointFrom(aPoint); + return path->ContainsPoint(point, {}); +} + +bool SVGGeometryElement::IsPointInStroke(const DOMPointInit& aPoint) { + // stroke-* attributes and the d attribute are presentation attributes, so we + // flush the layout before building the path. + if (nsCOMPtr doc = GetComposedDoc()) { + doc->FlushPendingNotifications(FlushType::Layout); + } + + RefPtr path = GetOrBuildPathForHitTest(); + if (!path) { + return false; + } + + auto point = GetPointFrom(aPoint); + bool res = false; + SVGGeometryProperty::DoForComputedStyle(this, [&](const ComputedStyle* s) { + // Per spec, we should take vector-effect into account. + if (s->StyleSVGReset()->HasNonScalingStroke()) { + auto mat = SVGContentUtils::GetCTM(this, true); + if (mat.HasNonTranslation()) { + // We have non-scaling-stroke as well as a non-translation transform. + // We should transform the path first then apply the stroke on the + // transformed path to preserve the stroke-width. + RefPtr builder = path->TransformedCopyToBuilder(mat); + + path = builder->Finish(); + point = mat.TransformPoint(point); + } + } + + SVGContentUtils::AutoStrokeOptions strokeOptions; + SVGContentUtils::GetStrokeOptions(&strokeOptions, this, s, nullptr); + + res = path->StrokeContainsPoint(strokeOptions, point, {}); + }); + + return res; +} + +float SVGGeometryElement::GetTotalLengthForBinding() { + // d is a presentation attribute, so make sure style is up to date: + FlushStyleIfNeeded(); + return GetTotalLength(); +} + +already_AddRefed SVGGeometryElement::GetPointAtLength( + float distance, ErrorResult& rv) { + // d is a presentation attribute, so make sure style is up to date: + FlushStyleIfNeeded(); + RefPtr path = GetOrBuildPathForMeasuring(); + if (!path) { + rv.ThrowInvalidStateError("No path available for measuring"); + return nullptr; + } + + RefPtr point = new DOMSVGPoint(path->ComputePointAtLength( + clamped(distance, 0.f, path->ComputeLength()))); + return point.forget(); +} + +float SVGGeometryElement::GetPathLengthScale(PathLengthScaleForType aFor) { + MOZ_ASSERT(aFor == eForTextPath || aFor == eForStroking, "Unknown enum"); + if (mPathLength.IsExplicitlySet()) { + float authorsPathLengthEstimate = mPathLength.GetAnimValue(); + if (authorsPathLengthEstimate >= 0) { + RefPtr path = GetOrBuildPathForMeasuring(); + if (!path) { + // The path is empty or invalid so its length must be zero and + // we know that 0 / authorsPathLengthEstimate = 0. + return 0.0; + } + if (aFor == eForTextPath) { + // For textPath, a transform on the referenced path affects the + // textPath layout, so when calculating the actual path length + // we need to take that into account. + gfxMatrix matrix = PrependLocalTransformsTo(gfxMatrix()); + if (!matrix.IsIdentity()) { + RefPtr builder = + path->TransformedCopyToBuilder(ToMatrix(matrix)); + path = builder->Finish(); + } + } + return path->ComputeLength() / authorsPathLengthEstimate; + } + } + return 1.0; +} + +already_AddRefed SVGGeometryElement::PathLength() { + return mPathLength.ToDOMAnimatedNumber(this); +} + +float SVGGeometryElement::GetTotalLength() { + RefPtr flat = GetOrBuildPathForMeasuring(); + return flat ? flat->ComputeLength() : 0.f; +} + +void SVGGeometryElement::FlushStyleIfNeeded() { + // Note: we still can set d property on other elements which don't have d + // attribute, but we don't look at the d property on them, so here we only + // care about the element with d attribute, i.e. SVG path element. + if (GetPathDataAttrName() != nsGkAtoms::d) { + return; + } + + RefPtr doc = GetComposedDoc(); + if (!doc) { + return; + } + + doc->FlushPendingNotifications(FlushType::Style); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGGeometryElement.h b/dom/svg/SVGGeometryElement.h new file mode 100644 index 0000000000..daad7c6c91 --- /dev/null +++ b/dom/svg/SVGGeometryElement.h @@ -0,0 +1,257 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGGEOMETRYELEMENT_H_ +#define DOM_SVG_SVGGEOMETRYELEMENT_H_ + +#include "mozilla/dom/SVGGraphicsElement.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/dom/SVGAnimatedNumber.h" + +namespace mozilla { + +struct SVGMark { + enum Type { + eStart, + eMid, + eEnd, + + eTypeCount + }; + + float x, y, angle; + Type type; + SVGMark(float aX, float aY, float aAngle, Type aType) + : x(aX), y(aY), angle(aAngle), type(aType) {} +}; + +namespace dom { + +class DOMSVGAnimatedNumber; +class DOMSVGPoint; + +using SVGGeometryElementBase = mozilla::dom::SVGGraphicsElement; + +class SVGGeometryElement : public SVGGeometryElementBase { + protected: + using CapStyle = mozilla::gfx::CapStyle; + using DrawTarget = mozilla::gfx::DrawTarget; + using FillRule = mozilla::gfx::FillRule; + using Float = mozilla::gfx::Float; + using Matrix = mozilla::gfx::Matrix; + using Path = mozilla::gfx::Path; + using Point = mozilla::gfx::Point; + using PathBuilder = mozilla::gfx::PathBuilder; + using Rect = mozilla::gfx::Rect; + using StrokeOptions = mozilla::gfx::StrokeOptions; + + public: + explicit SVGGeometryElement( + already_AddRefed&& aNodeInfo); + + NS_IMPL_FROMNODE_HELPER(SVGGeometryElement, IsSVGGeometryElement()) + + void AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, bool aNotify) override; + bool IsSVGGeometryElement() const override { return true; } + + /** + * Causes this element to discard any Path object that GetOrBuildPath may + * have cached. + */ + void ClearAnyCachedPath() final { mCachedPath = nullptr; } + + virtual bool AttributeDefinesGeometry(const nsAtom* aName); + + /** + * Returns true if this element's geometry depends on the width or height of + * its coordinate context (typically the viewport established by its nearest + * ancestor). In other words, returns true if one of the attributes for + * which AttributeDefinesGeometry returns true has a percentage value. + * + * This could be moved up to a more general class so it can be used for + * non-leaf elements, but that would require care and for now there's no need. + */ + bool GeometryDependsOnCoordCtx(); + + virtual bool IsMarkable(); + virtual void GetMarkPoints(nsTArray* aMarks); + + /** + * A method that can be faster than using a Moz2D Path and calling GetBounds/ + * GetStrokedBounds on it. It also helps us avoid rounding error for simple + * shapes and simple transforms where the Moz2D Path backends can fail to + * produce the clean integer bounds that content authors expect in some cases. + * + * If |aToNonScalingStrokeSpace| is non-null then |aBounds|, which is computed + * in bounds space, has the property that it's the smallest (axis-aligned) + * rectangular bound containing the image of this shape as stroked in + * non-scaling-stroke space. (When all transforms involved are rectilinear + * the bounds of the image of |aBounds| in non-scaling-stroke space will be + * tight, but if there are non-rectilinear transforms involved then that may + * be impossible and this method will return false). + * + * If |aToNonScalingStrokeSpace| is non-null then |*aToNonScalingStrokeSpace| + * must be non-singular. + */ + virtual bool GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) { + return false; + } + + /** + * For use with GetAsSimplePath. + */ + class SimplePath { + public: + SimplePath() + : mX(0.0), mY(0.0), mWidthOrX2(0.0), mHeightOrY2(0.0), mType(NONE) {} + bool IsPath() const { return mType != NONE; } + void SetRect(Float x, Float y, Float width, Float height) { + mX = x; + mY = y; + mWidthOrX2 = width; + mHeightOrY2 = height; + mType = RECT; + } + Rect AsRect() const { + MOZ_ASSERT(mType == RECT); + return Rect(mX, mY, mWidthOrX2, mHeightOrY2); + } + bool IsRect() const { return mType == RECT; } + void SetLine(Float x1, Float y1, Float x2, Float y2) { + mX = x1; + mY = y1; + mWidthOrX2 = x2; + mHeightOrY2 = y2; + mType = LINE; + } + Point Point1() const { + MOZ_ASSERT(mType == LINE); + return Point(mX, mY); + } + Point Point2() const { + MOZ_ASSERT(mType == LINE); + return Point(mWidthOrX2, mHeightOrY2); + } + bool IsLine() const { return mType == LINE; } + void Reset() { mType = NONE; } + + private: + enum Type { NONE, RECT, LINE }; + Float mX, mY, mWidthOrX2, mHeightOrY2; + Type mType; + }; + + /** + * For some platforms there is significant overhead to creating and painting + * a Moz2D Path object. For Rects and lines it is better to get the path data + * using this method and then use the optimized DrawTarget methods for + * filling/stroking rects and lines. + */ + virtual void GetAsSimplePath(SimplePath* aSimplePath) { + aSimplePath->Reset(); + } + + /** + * Returns a Path that can be used to paint, hit-test or calculate bounds for + * this element. May return nullptr if there is no [valid] path. The path + * that is created may be cached and returned on subsequent calls. + */ + virtual already_AddRefed GetOrBuildPath(const DrawTarget* aDrawTarget, + FillRule fillRule); + + /** + * The same as GetOrBuildPath, but bypasses the cache (neither returns any + * previously cached Path, nor caches the Path that in does return). + * this element. May return nullptr if there is no [valid] path. + */ + virtual already_AddRefed BuildPath(PathBuilder* aBuilder) = 0; + + /** + * Get the distances from the origin of the path segments. + * For non-path elements that's just 0 and the total length of the shape. + */ + virtual bool GetDistancesFromOriginToEndsOfVisibleSegments( + FallibleTArray* aOutput) { + aOutput->Clear(); + double distances[] = {0.0, GetTotalLength()}; + return aOutput->AppendElements(Span(distances), fallible); + } + + /** + * Returns a Path that can be used to measure the length of this elements + * path, or to find the position at a given distance along it. + * + * This is currently equivalent to calling GetOrBuildPath, but it may not be + * in the future. The reason for this function to be separate from + * GetOrBuildPath is because SVGPathData::BuildPath inserts small lines into + * the path if zero length subpaths are encountered, in order to implement + * the SVG specifications requirements that zero length subpaths should + * render circles/squares if stroke-linecap is round/square, respectively. + * In principle these inserted lines could interfere with path measurement, + * so we keep callers that are looking to do measurement separate in case we + * run into problems with the inserted lines negatively affecting measuring + * for content. + */ + virtual already_AddRefed GetOrBuildPathForMeasuring(); + + /** + * Return |true| if some geometry properties (|x|, |y|, etc) are changed + * because of CSS change. + */ + bool IsGeometryChangedViaCSS(ComputedStyle const& aNewStyle, + ComputedStyle const& aOldStyle) const; + + /** + * Returns the current computed value of the CSS property 'fill-rule' for + * this element. + */ + FillRule GetFillRule(); + + enum PathLengthScaleForType { eForTextPath, eForStroking }; + + /** + * Gets the ratio of the actual element's length to the content author's + * estimated length (as provided by the element's 'pathLength' attribute). + * This is used to scale stroke dashing, and to scale offsets along a + * textPath. + */ + float GetPathLengthScale(PathLengthScaleForType aFor); + + // WebIDL + already_AddRefed PathLength(); + MOZ_CAN_RUN_SCRIPT bool IsPointInFill(const DOMPointInit& aPoint); + MOZ_CAN_RUN_SCRIPT bool IsPointInStroke(const DOMPointInit& aPoint); + MOZ_CAN_RUN_SCRIPT float GetTotalLengthForBinding(); + MOZ_CAN_RUN_SCRIPT already_AddRefed GetPointAtLength( + float distance, ErrorResult& rv); + + protected: + // SVGElement method + NumberAttributesInfo GetNumberInfo() override; + + // d is a presentation attribute, so we would like to make sure style is + // up-to-date. This function flushes the style if the path attribute is d. + MOZ_CAN_RUN_SCRIPT void FlushStyleIfNeeded(); + + SVGAnimatedNumber mPathLength; + static NumberInfo sNumberInfo; + mutable RefPtr mCachedPath; + + private: + already_AddRefed GetOrBuildPathForHitTest(); + + float GetTotalLength(); +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGGEOMETRYELEMENT_H_ diff --git a/dom/svg/SVGGeometryProperty.cpp b/dom/svg/SVGGeometryProperty.cpp new file mode 100644 index 0000000000..7b58221b8d --- /dev/null +++ b/dom/svg/SVGGeometryProperty.cpp @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGGeometryProperty.h" +#include "SVGCircleElement.h" +#include "SVGEllipseElement.h" +#include "SVGForeignObjectElement.h" +#include "SVGImageElement.h" +#include "SVGRectElement.h" +#include "SVGUseElement.h" +#include "nsCSSValue.h" + +namespace mozilla::dom::SVGGeometryProperty { + +nsCSSUnit SpecifiedUnitTypeToCSSUnit(uint8_t aSpecifiedUnit) { + switch (aSpecifiedUnit) { + case SVGLength_Binding::SVG_LENGTHTYPE_NUMBER: + case SVGLength_Binding::SVG_LENGTHTYPE_PX: + return nsCSSUnit::eCSSUnit_Pixel; + + case SVGLength_Binding::SVG_LENGTHTYPE_MM: + return nsCSSUnit::eCSSUnit_Millimeter; + + case SVGLength_Binding::SVG_LENGTHTYPE_CM: + return nsCSSUnit::eCSSUnit_Centimeter; + + case SVGLength_Binding::SVG_LENGTHTYPE_IN: + return nsCSSUnit::eCSSUnit_Inch; + + case SVGLength_Binding::SVG_LENGTHTYPE_PT: + return nsCSSUnit::eCSSUnit_Point; + + case SVGLength_Binding::SVG_LENGTHTYPE_PC: + return nsCSSUnit::eCSSUnit_Pica; + + case SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE: + return nsCSSUnit::eCSSUnit_Percent; + + case SVGLength_Binding::SVG_LENGTHTYPE_EMS: + return nsCSSUnit::eCSSUnit_EM; + + case SVGLength_Binding::SVG_LENGTHTYPE_EXS: + return nsCSSUnit::eCSSUnit_XHeight; + + default: + MOZ_ASSERT_UNREACHABLE("Unknown unit type"); + return nsCSSUnit::eCSSUnit_Pixel; + } +} + +nsCSSPropertyID AttrEnumToCSSPropId(const SVGElement* aElement, + uint8_t aAttrEnum) { + // This is a very trivial function only applied to a few elements, + // so we want to avoid making it virtual. + if (aElement->IsSVGElement(nsGkAtoms::rect)) { + return SVGRectElement::GetCSSPropertyIdForAttrEnum(aAttrEnum); + } + if (aElement->IsSVGElement(nsGkAtoms::circle)) { + return SVGCircleElement::GetCSSPropertyIdForAttrEnum(aAttrEnum); + } + if (aElement->IsSVGElement(nsGkAtoms::ellipse)) { + return SVGEllipseElement::GetCSSPropertyIdForAttrEnum(aAttrEnum); + } + if (aElement->IsSVGElement(nsGkAtoms::image)) { + return SVGImageElement::GetCSSPropertyIdForAttrEnum(aAttrEnum); + } + if (aElement->IsSVGElement(nsGkAtoms::foreignObject)) { + return SVGForeignObjectElement::GetCSSPropertyIdForAttrEnum(aAttrEnum); + } + if (aElement->IsSVGElement(nsGkAtoms::use)) { + return SVGUseElement::GetCSSPropertyIdForAttrEnum(aAttrEnum); + } + return eCSSProperty_UNKNOWN; +} + +bool IsNonNegativeGeometryProperty(nsCSSPropertyID aProp) { + return aProp == eCSSProperty_r || aProp == eCSSProperty_rx || + aProp == eCSSProperty_ry || aProp == eCSSProperty_width || + aProp == eCSSProperty_height; +} + +bool ElementMapsLengthsToStyle(SVGElement const* aElement) { + return aElement->IsAnyOfSVGElements(nsGkAtoms::rect, nsGkAtoms::circle, + nsGkAtoms::ellipse, nsGkAtoms::image, + nsGkAtoms::foreignObject, nsGkAtoms::use); +} + +} // namespace mozilla::dom::SVGGeometryProperty diff --git a/dom/svg/SVGGeometryProperty.h b/dom/svg/SVGGeometryProperty.h new file mode 100644 index 0000000000..d5eb0e9d11 --- /dev/null +++ b/dom/svg/SVGGeometryProperty.h @@ -0,0 +1,279 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGGEOMETRYPROPERTY_H_ +#define DOM_SVG_SVGGEOMETRYPROPERTY_H_ + +#include "mozilla/SVGImageFrame.h" +#include "mozilla/dom/SVGElement.h" +#include "ComputedStyle.h" +#include "SVGAnimatedLength.h" +#include "nsComputedDOMStyle.h" +#include "nsGkAtoms.h" +#include "nsIFrame.h" +#include + +namespace mozilla::dom::SVGGeometryProperty { +namespace ResolverTypes { +struct LengthPercentNoAuto {}; +struct LengthPercentRXY {}; +struct LengthPercentWidthHeight {}; +} // namespace ResolverTypes + +namespace Tags { + +#define SVGGEOMETRYPROPERTY_GENERATETAG(tagName, resolver, direction, \ + styleStruct) \ + struct tagName { \ + using ResolverType = ResolverTypes::resolver; \ + constexpr static auto CtxDirection = SVGContentUtils::direction; \ + constexpr static auto Getter = &styleStruct::m##tagName; \ + } + +SVGGEOMETRYPROPERTY_GENERATETAG(X, LengthPercentNoAuto, X, nsStyleSVGReset); +SVGGEOMETRYPROPERTY_GENERATETAG(Y, LengthPercentNoAuto, Y, nsStyleSVGReset); +SVGGEOMETRYPROPERTY_GENERATETAG(Cx, LengthPercentNoAuto, X, nsStyleSVGReset); +SVGGEOMETRYPROPERTY_GENERATETAG(Cy, LengthPercentNoAuto, Y, nsStyleSVGReset); +SVGGEOMETRYPROPERTY_GENERATETAG(R, LengthPercentNoAuto, XY, nsStyleSVGReset); + +#undef SVGGEOMETRYPROPERTY_GENERATETAG + +struct Height; +struct Width { + using ResolverType = ResolverTypes::LengthPercentWidthHeight; + constexpr static auto CtxDirection = SVGContentUtils::X; + constexpr static auto Getter = &nsStylePosition::mWidth; + constexpr static auto SizeGetter = &gfx::Size::width; + static AspectRatio AspectRatioRelative(AspectRatio aAspectRatio) { + return aAspectRatio.Inverted(); + } + constexpr static uint32_t DefaultObjectSize = 300; + using CounterPart = Height; +}; +struct Height { + using ResolverType = ResolverTypes::LengthPercentWidthHeight; + constexpr static auto CtxDirection = SVGContentUtils::Y; + constexpr static auto Getter = &nsStylePosition::mHeight; + constexpr static auto SizeGetter = &gfx::Size::height; + static AspectRatio AspectRatioRelative(AspectRatio aAspectRatio) { + return aAspectRatio; + } + constexpr static uint32_t DefaultObjectSize = 150; + using CounterPart = Width; +}; + +struct Ry; +struct Rx { + using ResolverType = ResolverTypes::LengthPercentRXY; + constexpr static auto CtxDirection = SVGContentUtils::X; + constexpr static auto Getter = &nsStyleSVGReset::mRx; + using CounterPart = Ry; +}; +struct Ry { + using ResolverType = ResolverTypes::LengthPercentRXY; + constexpr static auto CtxDirection = SVGContentUtils::Y; + constexpr static auto Getter = &nsStyleSVGReset::mRy; + using CounterPart = Rx; +}; + +} // namespace Tags + +namespace details { +template +using AlwaysFloat = float; +using dummy = int[]; + +using CtxDirectionType = decltype(SVGContentUtils::X); + +template +float ResolvePureLengthPercentage(SVGElement* aElement, + const LengthPercentage& aLP) { + return aLP.ResolveToCSSPixelsWith( + [&] { return CSSCoord{SVGElementMetrics(aElement).GetAxisLength(CTD)}; }); +} + +template +float ResolveImpl(ComputedStyle const& aStyle, SVGElement* aElement, + ResolverTypes::LengthPercentNoAuto) { + auto const& value = aStyle.StyleSVGReset()->*Tag::Getter; + return ResolvePureLengthPercentage(aElement, value); +} + +template +float ResolveImpl(ComputedStyle const& aStyle, SVGElement* aElement, + ResolverTypes::LengthPercentWidthHeight) { + static_assert( + std::is_same{} || std::is_same{}, + "Wrong tag"); + + auto const& value = aStyle.StylePosition()->*Tag::Getter; + if (value.IsLengthPercentage()) { + return ResolvePureLengthPercentage( + aElement, value.AsLengthPercentage()); + } + + if (aElement->IsSVGElement(nsGkAtoms::image)) { + // It's not clear per SVG2 spec what should be done for values other + // than |auto| (e.g. |max-content|). We treat them as nonsense, thus + // using the initial value behavior, i.e. |auto|. + // The following procedure follows the Default Sizing Algorithm as + // specified in: + // https://svgwg.org/svg2-draft/embedded.html#ImageElement + + SVGImageFrame* imgf = do_QueryFrame(aElement->GetPrimaryFrame()); + if (!imgf) { + return 0.f; + } + + using Other = typename Tag::CounterPart; + auto const& valueOther = aStyle.StylePosition()->*Other::Getter; + + gfx::Size intrinsicImageSize; + AspectRatio aspectRatio; + if (!imgf->GetIntrinsicImageDimensions(intrinsicImageSize, aspectRatio)) { + // No image container, just return 0. + return 0.f; + } + + if (valueOther.IsLengthPercentage()) { + // We are |auto|, but the other side has specifed length. + float lengthOther = ResolvePureLengthPercentage( + aElement, valueOther.AsLengthPercentage()); + + if (aspectRatio) { + // Preserve aspect ratio if it's present. + return Other::AspectRatioRelative(aspectRatio) + .ApplyToFloat(lengthOther); + } + + float intrinsicLength = intrinsicImageSize.*Tag::SizeGetter; + if (intrinsicLength >= 0) { + // Use the intrinsic length if it's present. + return intrinsicLength; + } + + // No specified size, no aspect ratio, no intrinsic length, + // then use default size. + return Tag::DefaultObjectSize; + } + + // |width| and |height| are both |auto| + if (intrinsicImageSize.*Tag::SizeGetter >= 0) { + return intrinsicImageSize.*Tag::SizeGetter; + } + + if (intrinsicImageSize.*Other::SizeGetter >= 0 && aspectRatio) { + return Other::AspectRatioRelative(aspectRatio) + .ApplyTo(intrinsicImageSize.*Other::SizeGetter); + } + + if (aspectRatio) { + // Resolve as a contain constraint against the default object size. + auto defaultAspectRatioRelative = + AspectRatio{float(Other::DefaultObjectSize) / Tag::DefaultObjectSize}; + auto aspectRatioRelative = Tag::AspectRatioRelative(aspectRatio); + + if (defaultAspectRatioRelative < aspectRatioRelative) { + // Using default length in our side and the intrinsic aspect ratio, + // the other side cannot be contained. + return aspectRatioRelative.Inverted().ApplyTo(Other::DefaultObjectSize); + } + + return Tag::DefaultObjectSize; + } + + return Tag::DefaultObjectSize; + } + + // For other elements, |auto| and |max-content| etc. are treated as 0. + return 0.f; +} + +template +float ResolveImpl(ComputedStyle const& aStyle, SVGElement* aElement, + ResolverTypes::LengthPercentRXY) { + static_assert(std::is_same{} || std::is_same{}, + "Wrong tag"); + + auto const& value = aStyle.StyleSVGReset()->*Tag::Getter; + if (value.IsLengthPercentage()) { + return ResolvePureLengthPercentage( + aElement, value.AsLengthPercentage()); + } + + MOZ_ASSERT(value.IsAuto()); + using Rother = typename Tag::CounterPart; + auto const& valueOther = aStyle.StyleSVGReset()->*Rother::Getter; + + if (valueOther.IsAuto()) { + // Per SVG2, |Rx|, |Ry| resolve to 0 if both are |auto| + return 0.f; + } + + // If |Rx| is auto while |Ry| not, |Rx| gets the value of |Ry|. + return ResolvePureLengthPercentage( + aElement, valueOther.AsLengthPercentage()); +} + +} // namespace details + +template +float ResolveWith(const ComputedStyle& aStyle, const SVGElement* aElement) { + // TODO: There are a lot of utilities lacking const-ness in dom/svg. + // We should fix that problem and remove this `const_cast`. + return details::ResolveImpl(aStyle, const_cast(aElement), + typename Tag::ResolverType{}); +} + +template +bool DoForComputedStyle(const SVGElement* aElement, Func aFunc) { + if (const nsIFrame* f = aElement->GetPrimaryFrame()) { + aFunc(f->Style()); + return true; + } + + if (RefPtr computedStyle = + nsComputedDOMStyle::GetComputedStyleNoFlush(aElement)) { + aFunc(computedStyle.get()); + return true; + } + + return false; +} + +#define SVGGEOMETRYPROPERTY_EVAL_ALL(expr) \ + (void)details::dummy { 0, (static_cast(expr), 0)... } + +// To add support for new properties, or to handle special cases for +// existing properties, you can add a new tag in |Tags| and |ResolverTypes| +// namespace, then implement the behavior in |details::ResolveImpl|. +template +bool ResolveAll(const SVGElement* aElement, + details::AlwaysFloat*... aRes) { + bool res = DoForComputedStyle(aElement, [&](auto const* style) { + SVGGEOMETRYPROPERTY_EVAL_ALL(*aRes = ResolveWith(*style, aElement)); + }); + + if (res) { + return true; + } + + SVGGEOMETRYPROPERTY_EVAL_ALL(*aRes = 0); + return false; +} + +#undef SVGGEOMETRYPROPERTY_EVAL_ALL + +nsCSSUnit SpecifiedUnitTypeToCSSUnit(uint8_t aSpecifiedUnit); +nsCSSPropertyID AttrEnumToCSSPropId(const SVGElement* aElement, + uint8_t aAttrEnum); + +bool IsNonNegativeGeometryProperty(nsCSSPropertyID aProp); +bool ElementMapsLengthsToStyle(SVGElement const* aElement); + +} // namespace mozilla::dom::SVGGeometryProperty + +#endif // DOM_SVG_SVGGEOMETRYPROPERTY_H_ diff --git a/dom/svg/SVGGradientElement.cpp b/dom/svg/SVGGradientElement.cpp new file mode 100644 index 0000000000..9b59a92a6b --- /dev/null +++ b/dom/svg/SVGGradientElement.cpp @@ -0,0 +1,219 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGGradientElement.h" + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGGradientElementBinding.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGLinearGradientElementBinding.h" +#include "mozilla/dom/SVGRadialGradientElementBinding.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" +#include "DOMSVGAnimatedTransformList.h" +#include "nsGkAtoms.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(LinearGradient) +NS_IMPL_NS_NEW_SVG_ELEMENT(RadialGradient) + +namespace mozilla::dom { + +using namespace SVGGradientElement_Binding; +using namespace SVGUnitTypes_Binding; + +//--------------------- Gradients------------------------ + +SVGEnumMapping SVGGradientElement::sSpreadMethodMap[] = { + {nsGkAtoms::pad, SVG_SPREADMETHOD_PAD}, + {nsGkAtoms::reflect, SVG_SPREADMETHOD_REFLECT}, + {nsGkAtoms::repeat, SVG_SPREADMETHOD_REPEAT}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGGradientElement::sEnumInfo[2] = { + {nsGkAtoms::gradientUnits, sSVGUnitTypesMap, + SVG_UNIT_TYPE_OBJECTBOUNDINGBOX}, + {nsGkAtoms::spreadMethod, sSpreadMethodMap, SVG_SPREADMETHOD_PAD}}; + +SVGElement::StringInfo SVGGradientElement::sStringInfo[2] = { + {nsGkAtoms::href, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_XLink, true}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGGradientElement::SVGGradientElement( + already_AddRefed&& aNodeInfo) + : SVGGradientElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::EnumAttributesInfo SVGGradientElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGGradientElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +already_AddRefed +SVGGradientElement::GradientUnits() { + return mEnumAttributes[GRADIENTUNITS].ToDOMAnimatedEnum(this); +} + +already_AddRefed +SVGGradientElement::GradientTransform() { + // We're creating a DOM wrapper, so we must tell GetAnimatedTransformList + // to allocate the DOMSVGAnimatedTransformList if it hasn't already done so: + return DOMSVGAnimatedTransformList::GetDOMWrapper( + GetAnimatedTransformList(DO_ALLOCATE), this); +} + +already_AddRefed SVGGradientElement::SpreadMethod() { + return mEnumAttributes[SPREADMETHOD].ToDOMAnimatedEnum(this); +} + +already_AddRefed SVGGradientElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------Linear Gradients------------------------ + +JSObject* SVGLinearGradientElement::WrapNode( + JSContext* aCx, JS::Handle aGivenProto) { + return SVGLinearGradientElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGLinearGradientElement::sLengthInfo[4] = { + {nsGkAtoms::x1, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::y1, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, + {nsGkAtoms::x2, 100, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::y2, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, +}; + +//---------------------------------------------------------------------- +// Implementation + +SVGLinearGradientElement::SVGLinearGradientElement( + already_AddRefed&& aNodeInfo) + : SVGLinearGradientElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGLinearGradientElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGLinearGradientElement::X1() { + return mLengthAttributes[ATTR_X1].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGLinearGradientElement::Y1() { + return mLengthAttributes[ATTR_Y1].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGLinearGradientElement::X2() { + return mLengthAttributes[ATTR_X2].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGLinearGradientElement::Y2() { + return mLengthAttributes[ATTR_Y2].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGAnimatedTransformList* SVGGradientElement::GetAnimatedTransformList( + uint32_t aFlags) { + if (!mGradientTransform && (aFlags & DO_ALLOCATE)) { + mGradientTransform = MakeUnique(); + } + return mGradientTransform.get(); +} + +SVGElement::LengthAttributesInfo SVGLinearGradientElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +//-------------------------- Radial Gradients ---------------------------- + +JSObject* SVGRadialGradientElement::WrapNode( + JSContext* aCx, JS::Handle aGivenProto) { + return SVGRadialGradientElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGRadialGradientElement::sLengthInfo[6] = { + {nsGkAtoms::cx, 50, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::cy, 50, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, + {nsGkAtoms::r, 50, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::XY}, + {nsGkAtoms::fx, 50, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::fy, 50, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, + {nsGkAtoms::fr, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::XY}, +}; + +//---------------------------------------------------------------------- +// Implementation + +SVGRadialGradientElement::SVGRadialGradientElement( + already_AddRefed&& aNodeInfo) + : SVGRadialGradientElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGRadialGradientElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGRadialGradientElement::Cx() { + return mLengthAttributes[ATTR_CX].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGRadialGradientElement::Cy() { + return mLengthAttributes[ATTR_CY].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGRadialGradientElement::R() { + return mLengthAttributes[ATTR_R].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGRadialGradientElement::Fx() { + return mLengthAttributes[ATTR_FX].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGRadialGradientElement::Fy() { + return mLengthAttributes[ATTR_FY].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGRadialGradientElement::Fr() { + return mLengthAttributes[ATTR_FR].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::LengthAttributesInfo SVGRadialGradientElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGGradientElement.h b/dom/svg/SVGGradientElement.h new file mode 100644 index 0000000000..69d7f6ed95 --- /dev/null +++ b/dom/svg/SVGGradientElement.h @@ -0,0 +1,148 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGGRADIENTELEMENT_H_ +#define DOM_SVG_SVGGRADIENTELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedString.h" +#include "SVGAnimatedTransformList.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/UniquePtr.h" + +nsresult NS_NewSVGLinearGradientElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); +nsresult NS_NewSVGRadialGradientElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla { +class SVGGradientFrame; +class SVGLinearGradientFrame; +class SVGRadialGradientFrame; + +namespace dom { + +class DOMSVGAnimatedTransformList; + +//--------------------- Gradients------------------------ + +using SVGGradientElementBase = SVGElement; + +class SVGGradientElement : public SVGGradientElementBase { + friend class mozilla::SVGGradientFrame; + + protected: + explicit SVGGradientElement( + already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override = 0; + + public: + // nsIContent + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override = 0; + + virtual SVGAnimatedTransformList* GetAnimatedTransformList( + uint32_t aFlags = 0) override; + nsStaticAtom* GetTransformListAttrName() const override { + return nsGkAtoms::gradientTransform; + } + + // WebIDL + already_AddRefed GradientUnits(); + already_AddRefed GradientTransform(); + already_AddRefed SpreadMethod(); + already_AddRefed Href(); + + protected: + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { GRADIENTUNITS, SPREADMETHOD }; + SVGAnimatedEnumeration mEnumAttributes[2]; + static SVGEnumMapping sSpreadMethodMap[]; + static EnumInfo sEnumInfo[2]; + + enum { HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; + + // SVGGradientElement values + UniquePtr mGradientTransform; +}; + +//---------------------Linear Gradients------------------------ + +using SVGLinearGradientElementBase = SVGGradientElement; + +class SVGLinearGradientElement final : public SVGLinearGradientElementBase { + friend class mozilla::SVGLinearGradientFrame; + friend nsresult(::NS_NewSVGLinearGradientElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGLinearGradientElement( + already_AddRefed&& aNodeInfo); + virtual JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed X1(); + already_AddRefed Y1(); + already_AddRefed X2(); + already_AddRefed Y2(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + + enum { ATTR_X1, ATTR_Y1, ATTR_X2, ATTR_Y2 }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; +}; + +//-------------------------- Radial Gradients ---------------------------- + +using SVGRadialGradientElementBase = SVGGradientElement; + +class SVGRadialGradientElement final : public SVGRadialGradientElementBase { + friend class mozilla::SVGRadialGradientFrame; + friend nsresult(::NS_NewSVGRadialGradientElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + protected: + explicit SVGRadialGradientElement( + already_AddRefed&& aNodeInfo); + virtual JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed Cx(); + already_AddRefed Cy(); + already_AddRefed R(); + already_AddRefed Fx(); + already_AddRefed Fy(); + already_AddRefed Fr(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + + enum { ATTR_CX, ATTR_CY, ATTR_R, ATTR_FX, ATTR_FY, ATTR_FR }; + SVGAnimatedLength mLengthAttributes[6]; + static LengthInfo sLengthInfo[6]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGGRADIENTELEMENT_H_ diff --git a/dom/svg/SVGGraphicsElement.cpp b/dom/svg/SVGGraphicsElement.cpp new file mode 100644 index 0000000000..91ee7cdea0 --- /dev/null +++ b/dom/svg/SVGGraphicsElement.cpp @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGGraphicsElement.h" + +#include "mozilla/dom/BindContext.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/SVGGraphicsElementBinding.h" +#include "mozilla/dom/SVGMatrix.h" +#include "mozilla/dom/SVGRect.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/ISVGDisplayableFrame.h" +#include "mozilla/SVGContentUtils.h" +#include "mozilla/SVGTextFrame.h" +#include "mozilla/SVGUtils.h" + +#include "nsIContentInlines.h" +#include "nsLayoutUtils.h" + +namespace mozilla::dom { + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ADDREF_INHERITED(SVGGraphicsElement, SVGGraphicsElementBase) +NS_IMPL_RELEASE_INHERITED(SVGGraphicsElement, SVGGraphicsElementBase) + +NS_INTERFACE_MAP_BEGIN(SVGGraphicsElement) + NS_INTERFACE_MAP_ENTRY(mozilla::dom::SVGTests) +NS_INTERFACE_MAP_END_INHERITING(SVGGraphicsElementBase) + +//---------------------------------------------------------------------- +// Implementation + +SVGGraphicsElement::SVGGraphicsElement( + already_AddRefed&& aNodeInfo) + : SVGGraphicsElementBase(std::move(aNodeInfo)) {} + +SVGElement* SVGGraphicsElement::GetNearestViewportElement() { + return SVGContentUtils::GetNearestViewportElement(this); +} + +SVGElement* SVGGraphicsElement::GetFarthestViewportElement() { + return SVGContentUtils::GetOuterSVGElement(this); +} + +static already_AddRefed ZeroBBox(SVGGraphicsElement& aOwner) { + return MakeAndAddRef(&aOwner, gfx::Rect{0, 0, 0, 0}); +} + +already_AddRefed SVGGraphicsElement::GetBBox( + const SVGBoundingBoxOptions& aOptions) { + nsIFrame* frame = GetPrimaryFrame(FlushType::Layout); + + if (!frame || frame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + return ZeroBBox(*this); + } + ISVGDisplayableFrame* svgframe = do_QueryFrame(frame); + + if (!svgframe) { + if (!frame->IsInSVGTextSubtree()) { + return ZeroBBox(*this); + } + + // For , , the frame is an nsInlineFrame or + // nsBlockFrame, |svgframe| will be a nullptr. + // We implement their getBBox directly here instead of in + // SVGUtils::GetBBox, because SVGUtils::GetBBox is more + // or less used for other purpose elsewhere. e.g. gradient + // code assumes GetBBox of returns the bbox of the + // outer . + // TODO: cleanup this sort of usecase of SVGUtils::GetBBox, + // then move this code SVGUtils::GetBBox. + SVGTextFrame* text = + static_cast(nsLayoutUtils::GetClosestFrameOfType( + frame->GetParent(), LayoutFrameType::SVGText)); + + if (text->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + return ZeroBBox(*this); + } + + gfxRect rec = text->TransformFrameRectFromTextChild( + frame->GetRectRelativeToSelf(), frame); + + // Should also add the |x|, |y| of the SVGTextFrame itself, since + // the result obtained by TransformFrameRectFromTextChild doesn't + // include them. + rec.x += float(text->GetPosition().x) / AppUnitsPerCSSPixel(); + rec.y += float(text->GetPosition().y) / AppUnitsPerCSSPixel(); + + return do_AddRef(new SVGRect(this, ToRect(rec))); + } + + if (!NS_SVGNewGetBBoxEnabled()) { + return do_AddRef(new SVGRect( + this, ToRect(SVGUtils::GetBBox( + frame, SVGUtils::eBBoxIncludeFillGeometry | + SVGUtils::eUseUserSpaceOfUseElement)))); + } + uint32_t flags = 0; + if (aOptions.mFill) { + flags |= SVGUtils::eBBoxIncludeFill; + } + if (aOptions.mStroke) { + flags |= SVGUtils::eBBoxIncludeStroke; + } + if (aOptions.mMarkers) { + flags |= SVGUtils::eBBoxIncludeMarkers; + } + if (aOptions.mClipped) { + flags |= SVGUtils::eBBoxIncludeClipped; + } + if (flags == 0) { + return do_AddRef(new SVGRect(this, gfx::Rect())); + } + if (flags == SVGUtils::eBBoxIncludeMarkers || + flags == SVGUtils::eBBoxIncludeClipped) { + flags |= SVGUtils::eBBoxIncludeFill; + } + flags |= SVGUtils::eUseUserSpaceOfUseElement; + return do_AddRef(new SVGRect(this, ToRect(SVGUtils::GetBBox(frame, flags)))); +} + +already_AddRefed SVGGraphicsElement::GetCTM() { + Document* currentDoc = GetComposedDoc(); + if (currentDoc) { + // Flush all pending notifications so that our frames are up to date + currentDoc->FlushPendingNotifications(FlushType::Layout); + } + gfx::Matrix m = SVGContentUtils::GetCTM(this, false); + RefPtr mat = + m.IsSingular() ? nullptr : new SVGMatrix(ThebesMatrix(m)); + return mat.forget(); +} + +already_AddRefed SVGGraphicsElement::GetScreenCTM() { + Document* currentDoc = GetComposedDoc(); + if (currentDoc) { + // Flush all pending notifications so that our frames are up to date + currentDoc->FlushPendingNotifications(FlushType::Layout); + } + gfx::Matrix m = SVGContentUtils::GetCTM(this, true); + RefPtr mat = + m.IsSingular() ? nullptr : new SVGMatrix(ThebesMatrix(m)); + return mat.forget(); +} + +bool SVGGraphicsElement::IsSVGFocusable(bool* aIsFocusable, + int32_t* aTabIndex) { + // XXXedgar, maybe we could factor out the common code for SVG, HTML and + // MathML elements, see bug 1586011. + if (!IsInComposedDoc() || IsInDesignMode()) { + // In designMode documents we only allow focusing the document. + if (aTabIndex) { + *aTabIndex = -1; + } + + *aIsFocusable = false; + + return true; + } + + int32_t tabIndex = TabIndex(); + + if (aTabIndex) { + *aTabIndex = tabIndex; + } + + // If a tabindex is specified at all, or the default tabindex is 0, we're + // focusable + *aIsFocusable = tabIndex >= 0 || GetTabIndexAttrValue().isSome(); + + return false; +} + +bool SVGGraphicsElement::IsFocusableInternal(int32_t* aTabIndex, + bool aWithMouse) { + bool isFocusable = false; + IsSVGFocusable(&isFocusable, aTabIndex); + return isFocusable; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGGraphicsElement.h b/dom/svg/SVGGraphicsElement.h new file mode 100644 index 0000000000..0b4eacc8ec --- /dev/null +++ b/dom/svg/SVGGraphicsElement.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGGRAPHICSELEMENT_H_ +#define DOM_SVG_SVGGRAPHICSELEMENT_H_ + +#include "mozilla/dom/SVGTests.h" +#include "mozilla/dom/SVGTransformableElement.h" + +namespace mozilla::dom { + +using SVGGraphicsElementBase = SVGTransformableElement; + +class SVGGraphicsElement : public SVGGraphicsElementBase, public SVGTests { + protected: + explicit SVGGraphicsElement( + already_AddRefed&& aNodeInfo); + ~SVGGraphicsElement() = default; + + public: + // interfaces: + NS_DECL_ISUPPORTS_INHERITED + + NS_IMPL_FROMNODE_HELPER(SVGGraphicsElement, IsSVGGraphicsElement()) + + // WebIDL + SVGElement* GetNearestViewportElement(); + SVGElement* GetFarthestViewportElement(); + MOZ_CAN_RUN_SCRIPT + already_AddRefed GetBBox(const SVGBoundingBoxOptions&); + already_AddRefed GetCTM(); + already_AddRefed GetScreenCTM(); + + bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) override; + bool IsSVGGraphicsElement() const final { return true; } + + using nsINode::Clone; + // Overrides SVGTests. + SVGElement* AsSVGElement() final { return this; } + + protected: + // returns true if focusability has been definitively determined otherwise + // false + bool IsSVGFocusable(bool* aIsFocusable, int32_t* aTabIndex); + + template + bool IsInLengthInfo(const nsAtom* aAttribute, const T& aLengthInfos) const { + for (auto const& e : aLengthInfos) { + if (e.mName == aAttribute) { + return true; + } + } + return false; + } +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGGRAPHICSELEMENT_H_ diff --git a/dom/svg/SVGImageElement.cpp b/dom/svg/SVGImageElement.cpp new file mode 100644 index 0000000000..b28e8511fd --- /dev/null +++ b/dom/svg/SVGImageElement.cpp @@ -0,0 +1,318 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGImageElement.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/gfx/2D.h" +#include "nsCOMPtr.h" +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "imgINotificationObserver.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/SVGImageElementBinding.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/UserActivation.h" +#include "nsContentUtils.h" +#include "SVGGeometryProperty.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Image) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGImageElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGImageElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGImageElement::sLengthInfo[4] = { + {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::width, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::height, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, +}; + +SVGElement::StringInfo SVGImageElement::sStringInfo[2] = { + {nsGkAtoms::href, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_XLink, true}}; + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ISUPPORTS_INHERITED(SVGImageElement, SVGImageElementBase, + imgINotificationObserver, nsIImageLoadingContent) + +//---------------------------------------------------------------------- +// Implementation + +namespace SVGT = SVGGeometryProperty::Tags; + +SVGImageElement::SVGImageElement( + already_AddRefed&& aNodeInfo) + : SVGImageElementBase(std::move(aNodeInfo)) { + // We start out broken + AddStatesSilently(ElementState::BROKEN); +} + +SVGImageElement::~SVGImageElement() { nsImageLoadingContent::Destroy(); } + +nsCSSPropertyID SVGImageElement::GetCSSPropertyIdForAttrEnum( + uint8_t aAttrEnum) { + switch (aAttrEnum) { + case ATTR_X: + return eCSSProperty_x; + case ATTR_Y: + return eCSSProperty_y; + case ATTR_WIDTH: + return eCSSProperty_width; + case ATTR_HEIGHT: + return eCSSProperty_height; + default: + MOZ_ASSERT_UNREACHABLE("Unknown attr enum"); + return eCSSProperty_UNKNOWN; + } +} +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGImageElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGImageElement::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGImageElement::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGImageElement::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGImageElement::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +already_AddRefed +SVGImageElement::PreserveAspectRatio() { + return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this); +} + +already_AddRefed SVGImageElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +void SVGImageElement::GetDecoding(nsAString& aValue) { + GetEnumAttr(nsGkAtoms::decoding, kDecodingTableDefault->tag, aValue); +} + +already_AddRefed SVGImageElement::Decode(ErrorResult& aRv) { + return nsImageLoadingContent::QueueDecodeAsync(aRv); +} + +//---------------------------------------------------------------------- + +nsresult SVGImageElement::LoadSVGImage(bool aForce, bool aNotify) { + // resolve href attribute + nsIURI* baseURI = GetBaseURI(); + + nsAutoString href; + if (mStringAttributes[HREF].IsExplicitlySet()) { + mStringAttributes[HREF].GetAnimValue(href, this); + } else { + mStringAttributes[XLINK_HREF].GetAnimValue(href, this); + } + href.Trim(" \t\n\r"); + + if (baseURI && !href.IsEmpty()) NS_MakeAbsoluteURI(href, href, baseURI); + + // Mark channel as urgent-start before load image if the image load is + // initaiated by a user interaction. + mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput(); + + return LoadImage(href, aForce, aNotify, eImageLoadType_Normal); +} + +bool SVGImageElement::ShouldLoadImage() const { + return LoadingEnabled() && OwnerDoc()->ShouldLoadImages(); +} + +Rect SVGImageElement::GeometryBounds(const Matrix& aToBoundsSpace) { + Rect rect; + + DebugOnly ok = + SVGGeometryProperty::ResolveAll(this, &rect.x, &rect.y, + &rect.width, &rect.height); + MOZ_ASSERT(ok, "SVGGeometryProperty::ResolveAll failed"); + + if (rect.IsEmpty()) { + // Rendering of the element disabled + rect.SetEmpty(); // Make sure width/height are zero and not negative + } + + return aToBoundsSpace.TransformBounds(rect); +} + +//---------------------------------------------------------------------- +// EventTarget methods: + +void SVGImageElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) { + nsImageLoadingContent::AsyncEventRunning(aEvent); +} + +//---------------------------------------------------------------------- +// nsImageLoadingContent methods: + +CORSMode SVGImageElement::GetCORSMode() { + return AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin)); +} + +//---------------------------------------------------------------------- +// nsIContent methods: + +bool SVGImageElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) { + if (aNamespaceID == kNameSpaceID_None) { + if (aAttribute == nsGkAtoms::crossorigin) { + ParseCORSValue(aValue, aResult); + return true; + } + if (aAttribute == nsGkAtoms::decoding) { + return aResult.ParseEnumValue(aValue, kDecodingTable, false, + kDecodingTableDefault); + } + } + + return SVGImageElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue, + aMaybeScriptedPrincipal, aResult); +} + +void SVGImageElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, + bool aNotify) { + if (aName == nsGkAtoms::href && (aNamespaceID == kNameSpaceID_None || + aNamespaceID == kNameSpaceID_XLink)) { + if (aValue) { + if (ShouldLoadImage()) { + LoadSVGImage(true, aNotify); + } + } else { + CancelImageRequests(aNotify); + } + } else if (aNamespaceID == kNameSpaceID_None) { + if (aName == nsGkAtoms::decoding) { + // Request sync or async image decoding. + SetSyncDecodingHint( + aValue && static_cast(aValue->GetEnumValue()) == + ImageDecodingType::Sync); + } else if (aName == nsGkAtoms::crossorigin) { + if (aNotify && GetCORSMode() != AttrValueToCORSMode(aOldValue) && + ShouldLoadImage()) { + ForceReload(aNotify, IgnoreErrors()); + } + } + } + + return SVGImageElementBase::AfterSetAttr( + aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); +} + +void SVGImageElement::MaybeLoadSVGImage() { + if ((mStringAttributes[HREF].IsExplicitlySet() || + mStringAttributes[XLINK_HREF].IsExplicitlySet()) && + (NS_FAILED(LoadSVGImage(false, true)) || !LoadingEnabled())) { + CancelImageRequests(true); + } +} + +nsresult SVGImageElement::BindToTree(BindContext& aContext, nsINode& aParent) { + nsresult rv = SVGImageElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + nsImageLoadingContent::BindToTree(aContext, aParent); + + if ((mStringAttributes[HREF].IsExplicitlySet() || + mStringAttributes[XLINK_HREF].IsExplicitlySet()) && + ShouldLoadImage()) { + nsContentUtils::AddScriptRunner( + NewRunnableMethod("dom::SVGImageElement::MaybeLoadSVGImage", this, + &SVGImageElement::MaybeLoadSVGImage)); + } + + return rv; +} + +void SVGImageElement::UnbindFromTree(bool aNullParent) { + nsImageLoadingContent::UnbindFromTree(aNullParent); + SVGImageElementBase::UnbindFromTree(aNullParent); +} + +ElementState SVGImageElement::IntrinsicState() const { + return SVGImageElementBase::IntrinsicState() | + nsImageLoadingContent::ImageState(); +} + +void SVGImageElement::DestroyContent() { + nsImageLoadingContent::Destroy(); + SVGImageElementBase::DestroyContent(); +} + +NS_IMETHODIMP_(bool) +SVGImageElement::IsAttributeMapped(const nsAtom* name) const { + return IsInLengthInfo(name, sLengthInfo) || + SVGImageElementBase::IsAttributeMapped(name); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +bool SVGImageElement::HasValidDimensions() const { + float width, height; + + if (SVGGeometryProperty::ResolveAll(this, &width, + &height)) { + return width > 0 && height > 0; + } + // This function might be called for an element in display:none subtree + // (e.g. SMIL animateMotion), we fall back to use SVG attributes. + return (!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() || + mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) && + (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() || + mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0); +} + +SVGElement::LengthAttributesInfo SVGImageElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +SVGAnimatedPreserveAspectRatio* +SVGImageElement::GetAnimatedPreserveAspectRatio() { + return &mPreserveAspectRatio; +} + +SVGElement::StringAttributesInfo SVGImageElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGImageElement.h b/dom/svg/SVGImageElement.h new file mode 100644 index 0000000000..464a95f9ee --- /dev/null +++ b/dom/svg/SVGImageElement.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGIMAGEELEMENT_H_ +#define DOM_SVG_SVGIMAGEELEMENT_H_ + +#include "nsImageLoadingContent.h" +#include "mozilla/dom/SVGAnimatedLength.h" +#include "mozilla/dom/SVGAnimatedString.h" +#include "mozilla/dom/SVGGeometryElement.h" +#include "mozilla/dom/SVGAnimatedPreserveAspectRatio.h" +#include "mozilla/gfx/2D.h" + +nsresult NS_NewSVGImageElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla { +class SVGImageFrame; + +namespace dom { +class DOMSVGAnimatedPreserveAspectRatio; + +using SVGImageElementBase = SVGGraphicsElement; + +class SVGImageElement final : public SVGImageElementBase, + public nsImageLoadingContent { + friend class mozilla::SVGImageFrame; + + protected: + explicit SVGImageElement( + already_AddRefed&& aNodeInfo); + virtual ~SVGImageElement(); + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + friend nsresult(::NS_NewSVGImageElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + public: + // interfaces: + + NS_DECL_ISUPPORTS_INHERITED + + // EventTarget + void AsyncEventRunning(AsyncEventDispatcher* aEvent) override; + + // nsImageLoadingContent interface + CORSMode GetCORSMode() override; + + // nsIContent interface + bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) override; + void AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, bool aNotify) override; + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + void UnbindFromTree(bool aNullParent) override; + + ElementState IntrinsicState() const override; + + void DestroyContent() override; + + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* name) const override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + void MaybeLoadSVGImage(); + + // WebIDL + already_AddRefed X(); + already_AddRefed Y(); + already_AddRefed Width(); + already_AddRefed Height(); + already_AddRefed PreserveAspectRatio(); + already_AddRefed Href(); + void GetCrossOrigin(nsAString& aCrossOrigin) { + // Null for both missing and invalid defaults is ok, since we + // always parse to an enum value, so we don't need an invalid + // default, and we _want_ the missing default to be null. + GetEnumAttr(nsGkAtoms::crossorigin, nullptr, aCrossOrigin); + } + void SetCrossOrigin(const nsAString& aCrossOrigin, ErrorResult& aError) { + SetOrRemoveNullableStringAttr(nsGkAtoms::crossorigin, aCrossOrigin, aError); + } + + void SetDecoding(const nsAString& aDecoding, ErrorResult& aError) { + SetAttr(nsGkAtoms::decoding, aDecoding, aError); + } + void GetDecoding(nsAString& aValue); + + already_AddRefed Decode(ErrorResult& aRv); + + static nsCSSPropertyID GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum); + + gfx::Rect GeometryBounds(const gfx::Matrix& aToBoundsSpace); + + protected: + nsresult LoadSVGImage(bool aForce, bool aNotify); + bool ShouldLoadImage() const; + + LengthAttributesInfo GetLengthInfo() override; + SVGAnimatedPreserveAspectRatio* GetAnimatedPreserveAspectRatio() override; + StringAttributesInfo GetStringInfo() override; + + // Override for nsImageLoadingContent. + nsIContent* AsContent() override { return this; } + + enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; + + SVGAnimatedPreserveAspectRatio mPreserveAspectRatio; + + enum { HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGIMAGEELEMENT_H_ diff --git a/dom/svg/SVGIntegerPairSMILType.cpp b/dom/svg/SVGIntegerPairSMILType.cpp new file mode 100644 index 0000000000..838522b272 --- /dev/null +++ b/dom/svg/SVGIntegerPairSMILType.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGIntegerPairSMILType.h" + +#include "mozilla/SMILValue.h" +#include "nsMathUtils.h" +#include "nsDebug.h" + +namespace mozilla { + +void SVGIntegerPairSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + + aValue.mU.mIntPair[0] = 0; + aValue.mU.mIntPair[1] = 0; + aValue.mType = this; +} + +void SVGIntegerPairSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value"); + aValue.mU.mIntPair[0] = 0; + aValue.mU.mIntPair[1] = 0; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGIntegerPairSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value"); + + aDest.mU.mIntPair[0] = aSrc.mU.mIntPair[0]; + aDest.mU.mIntPair[1] = aSrc.mU.mIntPair[1]; + return NS_OK; +} + +bool SVGIntegerPairSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected type for SMIL value"); + + return aLeft.mU.mIntPair[0] == aRight.mU.mIntPair[0] && + aLeft.mU.mIntPair[1] == aRight.mU.mIntPair[1]; +} + +nsresult SVGIntegerPairSMILType::Add(SMILValue& aDest, + const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aValueToAdd.mType == aDest.mType, "Trying to add invalid types"); + MOZ_ASSERT(aValueToAdd.mType == this, "Unexpected source type"); + + aDest.mU.mIntPair[0] += aValueToAdd.mU.mIntPair[0] * aCount; + aDest.mU.mIntPair[1] += aValueToAdd.mU.mIntPair[1] * aCount; + + return NS_OK; +} + +nsresult SVGIntegerPairSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == aTo.mType, "Trying to compare different types"); + MOZ_ASSERT(aFrom.mType == this, "Unexpected source type"); + + double delta[2]; + delta[0] = aTo.mU.mIntPair[0] - aFrom.mU.mIntPair[0]; + delta[1] = aTo.mU.mIntPair[1] - aFrom.mU.mIntPair[1]; + + aDistance = NS_hypot(delta[0], delta[1]); + return NS_OK; +} + +nsresult SVGIntegerPairSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + + double currentVal[2]; + currentVal[0] = + aStartVal.mU.mIntPair[0] + + (aEndVal.mU.mIntPair[0] - aStartVal.mU.mIntPair[0]) * aUnitDistance; + currentVal[1] = + aStartVal.mU.mIntPair[1] + + (aEndVal.mU.mIntPair[1] - aStartVal.mU.mIntPair[1]) * aUnitDistance; + + aResult.mU.mIntPair[0] = NS_lround(currentVal[0]); + aResult.mU.mIntPair[1] = NS_lround(currentVal[1]); + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGIntegerPairSMILType.h b/dom/svg/SVGIntegerPairSMILType.h new file mode 100644 index 0000000000..c7c439c692 --- /dev/null +++ b/dom/svg/SVGIntegerPairSMILType.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGINTEGERPAIRSMILTYPE_H_ +#define DOM_SVG_SVGINTEGERPAIRSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +namespace mozilla { + +class SMILValue; + +class SVGIntegerPairSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGIntegerPairSMILType* Singleton() { + static SVGIntegerPairSMILType sSingleton; + return &sSingleton; + } + + protected: + // SMILType Methods + // ------------------- + void Init(SMILValue& aValue) const override; + void Destroy(SMILValue&) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGIntegerPairSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGINTEGERPAIRSMILTYPE_H_ diff --git a/dom/svg/SVGLength.cpp b/dom/svg/SVGLength.cpp new file mode 100644 index 0000000000..7a0bbf6437 --- /dev/null +++ b/dom/svg/SVGLength.cpp @@ -0,0 +1,255 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGLength.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "nsTextFormatter.h" +#include "SVGContentUtils.h" +#include +#include + +using namespace mozilla::dom; +using namespace mozilla::dom::SVGLength_Binding; + +namespace mozilla { + +void SVGLength::GetValueAsString(nsAString& aValue) const { + nsTextFormatter::ssprintf(aValue, u"%g", (double)mValue); + + nsAutoString unitString; + GetUnitString(unitString, mUnit); + aValue.Append(unitString); +} + +bool SVGLength::SetValueFromString(const nsAString& aString) { + bool success; + auto token = SVGContentUtils::GetAndEnsureOneToken(aString, success); + + if (!success) { + return false; + } + + RangedPtr iter = SVGContentUtils::GetStartRangedPtr(token); + const RangedPtr end = SVGContentUtils::GetEndRangedPtr(token); + + float value; + + if (!SVGContentUtils::ParseNumber(iter, end, value)) { + return false; + } + + const nsAString& units = Substring(iter.get(), end.get()); + uint16_t unitType = GetUnitTypeForString(units); + if (unitType == SVG_LENGTHTYPE_UNKNOWN) { + return false; + } + mValue = value; + mUnit = uint8_t(unitType); + return true; +} + +inline static bool IsAbsoluteUnit(uint8_t aUnit) { + return aUnit >= SVGLength_Binding::SVG_LENGTHTYPE_CM && + aUnit <= SVGLength_Binding::SVG_LENGTHTYPE_PC; +} + +/** + * Helper to convert between different CSS absolute units without the need for + * an element, which provides more flexibility at the DOM level (and without + * the need for an intermediary conversion to user units, which avoids + * unnecessary overhead and rounding error). + * + * Example usage: to find out how many centimeters there are per inch: + * + * GetAbsUnitsPerAbsUnit(SVGLength_Binding::SVG_LENGTHTYPE_CM, + * SVGLength_Binding::SVG_LENGTHTYPE_IN) + */ +inline static float GetAbsUnitsPerAbsUnit(uint8_t aUnits, uint8_t aPerUnit) { + MOZ_ASSERT(IsAbsoluteUnit(aUnits), "Not a CSS absolute unit"); + MOZ_ASSERT(IsAbsoluteUnit(aPerUnit), "Not a CSS absolute unit"); + + float CSSAbsoluteUnitConversionFactors[5][5] = { + // columns: cm, mm, in, pt, pc + // cm per...: + {1.0f, 0.1f, 2.54f, 0.035277777777777778f, 0.42333333333333333f}, + // mm per...: + {10.0f, 1.0f, 25.4f, 0.35277777777777778f, 4.2333333333333333f}, + // in per...: + {0.39370078740157481f, 0.039370078740157481f, 1.0f, 0.013888888888888889f, + 0.16666666666666667f}, + // pt per...: + {28.346456692913386f, 2.8346456692913386f, 72.0f, 1.0f, 12.0f}, + // pc per...: + {2.3622047244094489f, 0.23622047244094489f, 6.0f, 0.083333333333333333f, + 1.0f}}; + + // First absolute unit is SVG_LENGTHTYPE_CM = 6 + return CSSAbsoluteUnitConversionFactors[aUnits - 6][aPerUnit - 6]; +} + +float SVGLength::GetValueInSpecifiedUnit(uint8_t aUnit, + const SVGElement* aElement, + uint8_t aAxis) const { + if (aUnit == mUnit) { + return mValue; + } + if ((aUnit == SVGLength_Binding::SVG_LENGTHTYPE_NUMBER && + mUnit == SVGLength_Binding::SVG_LENGTHTYPE_PX) || + (aUnit == SVGLength_Binding::SVG_LENGTHTYPE_PX && + mUnit == SVGLength_Binding::SVG_LENGTHTYPE_NUMBER)) { + return mValue; + } + if (IsAbsoluteUnit(aUnit) && IsAbsoluteUnit(mUnit)) { + return mValue * GetAbsUnitsPerAbsUnit(aUnit, mUnit); + } + + // Otherwise we do a two step conversion via user units. This can only + // succeed if aElement is non-null (although that's not sufficient to + // guarantee success). + + float userUnitsPerCurrentUnit = GetUserUnitsPerUnit(aElement, aAxis); + float userUnitsPerNewUnit = + SVGLength(0.0f, aUnit).GetUserUnitsPerUnit(aElement, aAxis); + + NS_ASSERTION( + userUnitsPerCurrentUnit >= 0 || !std::isfinite(userUnitsPerCurrentUnit), + "bad userUnitsPerCurrentUnit"); + NS_ASSERTION(userUnitsPerNewUnit >= 0 || !std::isfinite(userUnitsPerNewUnit), + "bad userUnitsPerNewUnit"); + + float value = mValue * userUnitsPerCurrentUnit / userUnitsPerNewUnit; + + // userUnitsPerCurrentUnit could be infinity, or userUnitsPerNewUnit could + // be zero. + if (std::isfinite(value)) { + return value; + } + return std::numeric_limits::quiet_NaN(); +} + +#define INCHES_PER_MM_FLOAT float(0.0393700787) +#define INCHES_PER_CM_FLOAT float(0.393700787) + +float SVGLength::GetUserUnitsPerUnit(const SVGElement* aElement, + uint8_t aAxis) const { + switch (mUnit) { + case SVGLength_Binding::SVG_LENGTHTYPE_NUMBER: + case SVGLength_Binding::SVG_LENGTHTYPE_PX: + return 1.0f; + case SVGLength_Binding::SVG_LENGTHTYPE_MM: + return INCHES_PER_MM_FLOAT * GetUserUnitsPerInch(); + case SVGLength_Binding::SVG_LENGTHTYPE_CM: + return INCHES_PER_CM_FLOAT * GetUserUnitsPerInch(); + case SVGLength_Binding::SVG_LENGTHTYPE_IN: + return GetUserUnitsPerInch(); + case SVGLength_Binding::SVG_LENGTHTYPE_PT: + return (1.0f / POINTS_PER_INCH_FLOAT) * GetUserUnitsPerInch(); + case SVGLength_Binding::SVG_LENGTHTYPE_PC: + return (12.0f / POINTS_PER_INCH_FLOAT) * GetUserUnitsPerInch(); + case SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE: + return GetUserUnitsPerPercent(aElement, aAxis); + case SVGLength_Binding::SVG_LENGTHTYPE_EMS: + return SVGContentUtils::GetFontSize(const_cast(aElement)); + case SVGLength_Binding::SVG_LENGTHTYPE_EXS: + return SVGContentUtils::GetFontXHeight(const_cast(aElement)); + default: + MOZ_ASSERT_UNREACHABLE("Unknown unit type"); + return std::numeric_limits::quiet_NaN(); + } +} + +/* static */ +float SVGLength::GetUserUnitsPerPercent(const SVGElement* aElement, + uint8_t aAxis) { + if (aElement) { + dom::SVGViewportElement* viewportElement = aElement->GetCtx(); + if (viewportElement) { + return std::max(viewportElement->GetLength(aAxis) / 100.0f, 0.0f); + } + } + return std::numeric_limits::quiet_NaN(); +} + +// Helpers: + +/* static */ +void SVGLength::GetUnitString(nsAString& aUnit, uint16_t aUnitType) { + switch (aUnitType) { + case SVG_LENGTHTYPE_NUMBER: + aUnit.Truncate(); + return; + case SVG_LENGTHTYPE_PERCENTAGE: + aUnit.AssignLiteral("%"); + return; + case SVG_LENGTHTYPE_EMS: + aUnit.AssignLiteral("em"); + return; + case SVG_LENGTHTYPE_EXS: + aUnit.AssignLiteral("ex"); + return; + case SVG_LENGTHTYPE_PX: + aUnit.AssignLiteral("px"); + return; + case SVG_LENGTHTYPE_CM: + aUnit.AssignLiteral("cm"); + return; + case SVG_LENGTHTYPE_MM: + aUnit.AssignLiteral("mm"); + return; + case SVG_LENGTHTYPE_IN: + aUnit.AssignLiteral("in"); + return; + case SVG_LENGTHTYPE_PT: + aUnit.AssignLiteral("pt"); + return; + case SVG_LENGTHTYPE_PC: + aUnit.AssignLiteral("pc"); + return; + } + MOZ_ASSERT_UNREACHABLE( + "Unknown unit type! Someone's using an SVGLength " + "with an invalid unit?"); +} + +/* static */ +uint16_t SVGLength::GetUnitTypeForString(const nsAString& aUnit) { + if (aUnit.IsEmpty()) { + return SVG_LENGTHTYPE_NUMBER; + } + if (aUnit.EqualsLiteral("%")) { + return SVG_LENGTHTYPE_PERCENTAGE; + } + if (aUnit.LowerCaseEqualsLiteral("em")) { + return SVG_LENGTHTYPE_EMS; + } + if (aUnit.LowerCaseEqualsLiteral("ex")) { + return SVG_LENGTHTYPE_EXS; + } + if (aUnit.LowerCaseEqualsLiteral("px")) { + return SVG_LENGTHTYPE_PX; + } + if (aUnit.LowerCaseEqualsLiteral("cm")) { + return SVG_LENGTHTYPE_CM; + } + if (aUnit.LowerCaseEqualsLiteral("mm")) { + return SVG_LENGTHTYPE_MM; + } + if (aUnit.LowerCaseEqualsLiteral("in")) { + return SVG_LENGTHTYPE_IN; + } + if (aUnit.LowerCaseEqualsLiteral("pt")) { + return SVG_LENGTHTYPE_PT; + } + if (aUnit.LowerCaseEqualsLiteral("pc")) { + return SVG_LENGTHTYPE_PC; + } + return SVG_LENGTHTYPE_UNKNOWN; +} + +} // namespace mozilla diff --git a/dom/svg/SVGLength.h b/dom/svg/SVGLength.h new file mode 100644 index 0000000000..ab0cef140a --- /dev/null +++ b/dom/svg/SVGLength.h @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGLENGTH_H_ +#define DOM_SVG_SVGLENGTH_H_ + +#include "nsDebug.h" +#include "nsMathUtils.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/dom/SVGLengthBinding.h" + +namespace mozilla { + +namespace dom { +class SVGElement; +} + +/** + * This SVGLength class is currently used for SVGLength *list* attributes only. + * The class that is currently used for attributes is + * SVGAnimatedLength. + * + * The member mUnit should always be valid, but the member mValue may be + * numeric_limits::quiet_NaN() under one circumstances (see the comment + * in SetValueAndUnit below). Even if mValue is valid, some methods may return + * numeric_limits::quiet_NaN() if they involve a unit conversion that + * fails - see comments below. + * + * The DOM wrapper class for this class is DOMSVGLength. + */ +class SVGLength { + public: + SVGLength() + : mValue(0.0f), + mUnit(dom::SVGLength_Binding::SVG_LENGTHTYPE_UNKNOWN) // caught by + // IsValid() + {} + + SVGLength(float aValue, uint8_t aUnit) : mValue(aValue), mUnit(aUnit) { + NS_ASSERTION(IsValid(), "Constructed an invalid length"); + } + + bool operator==(const SVGLength& rhs) const { + return mValue == rhs.mValue && mUnit == rhs.mUnit; + } + + void GetValueAsString(nsAString& aValue) const; + + /** + * This method returns true, unless there was a parse failure, in which + * case it returns false (and the length is left unchanged). + */ + bool SetValueFromString(const nsAString& aString); + + /** + * This will usually return a valid, finite number. There is one exception + * though - see the comment in SetValueAndUnit(). + */ + float GetValueInCurrentUnits() const { return mValue; } + + uint8_t GetUnit() const { return mUnit; } + + void SetValueInCurrentUnits(float aValue) { + mValue = aValue; + NS_ASSERTION(IsValid(), "Set invalid SVGLength"); + } + + void SetValueAndUnit(float aValue, uint8_t aUnit) { + mValue = aValue; + mUnit = aUnit; + + // IsValid() should always be true, with one exception: if + // SVGLengthListSMILType has to convert between unit types and the unit + // conversion is undefined, it will end up passing in and setting + // numeric_limits::quiet_NaN(). Because of that we only check the + // unit here, and allow mValue to be invalid. The painting code has to be + // able to handle NaN anyway, since conversion to user units may fail in + // general. + + NS_ASSERTION(IsValidUnitType(mUnit), "Set invalid SVGLength"); + } + + /** + * If it's not possible to convert this length's value to user units, then + * this method will return numeric_limits::quiet_NaN(). + */ + float GetValueInUserUnits(const dom::SVGElement* aElement, + uint8_t aAxis) const { + return mValue * GetUserUnitsPerUnit(aElement, aAxis); + } + + /** + * Get this length's value in the units specified. + * + * This method returns numeric_limits::quiet_NaN() if it is not + * possible to convert the value to the specified unit. + */ + float GetValueInSpecifiedUnit(uint8_t aUnit, const dom::SVGElement* aElement, + uint8_t aAxis) const; + + bool IsPercentage() const { + return mUnit == dom::SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE; + } + + static bool IsValidUnitType(uint16_t aUnitType) { + return aUnitType > dom::SVGLength_Binding::SVG_LENGTHTYPE_UNKNOWN && + aUnitType <= dom::SVGLength_Binding::SVG_LENGTHTYPE_PC; + } + + static void GetUnitString(nsAString& aUnit, uint16_t aUnitType); + + static uint16_t GetUnitTypeForString(const nsAString& aUnit); + + /** + * Returns the number of user units per current unit. + * + * This method returns numeric_limits::quiet_NaN() if the conversion + * factor between the length's current unit and user units is undefined (see + * the comments for GetUserUnitsPerInch and GetUserUnitsPerPercent). + */ + float GetUserUnitsPerUnit(const dom::SVGElement* aElement, + uint8_t aAxis) const; + + private: +#ifdef DEBUG + bool IsValid() const { + return std::isfinite(mValue) && IsValidUnitType(mUnit); + } +#endif + + /** + * The conversion factor between user units (CSS px) and CSS inches is + * constant: 96 px per inch. + */ + static float GetUserUnitsPerInch() { return 96.0; } + + /** + * The conversion factor between user units and percentage units depends on + * aElement being non-null, and on aElement having a viewport element + * ancestor with only appropriate SVG elements between aElement and that + * ancestor. If that's not the case, then the conversion factor is undefined. + * + * This function returns a non-negative value if the conversion factor is + * defined, otherwise it returns numeric_limits::quiet_NaN(). + */ + static float GetUserUnitsPerPercent(const dom::SVGElement* aElement, + uint8_t aAxis); + + float mValue; + uint8_t mUnit; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGLENGTH_H_ diff --git a/dom/svg/SVGLengthList.cpp b/dom/svg/SVGLengthList.cpp new file mode 100644 index 0000000000..9f3cfa39a5 --- /dev/null +++ b/dom/svg/SVGLengthList.cpp @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGLengthList.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" +#include "nsError.h" +#include "nsString.h" +#include "SVGElement.h" +#include "SVGAnimatedLengthList.h" +#include "SVGContentUtils.h" +#include "SVGLength.h" + +namespace mozilla { + +nsresult SVGLengthList::CopyFrom(const SVGLengthList& rhs) { + if (!mLengths.Assign(rhs.mLengths, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +void SVGLengthList::GetValueAsString(nsAString& aValue) const { + aValue.Truncate(); + uint32_t last = mLengths.Length() - 1; + for (uint32_t i = 0; i < mLengths.Length(); ++i) { + nsAutoString length; + mLengths[i].GetValueAsString(length); + // We ignore OOM, since it's not useful for us to return an error. + aValue.Append(length); + if (i != last) { + aValue.Append(' '); + } + } +} + +nsresult SVGLengthList::SetValueFromString(const nsAString& aValue) { + SVGLengthList temp; + + nsCharSeparatedTokenizerTemplate + tokenizer(aValue, ','); + + while (tokenizer.hasMoreTokens()) { + SVGLength length; + if (!length.SetValueFromString(tokenizer.nextToken())) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + if (!temp.AppendItem(length)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + if (tokenizer.separatorAfterCurrentToken()) { + return NS_ERROR_DOM_SYNTAX_ERR; // trailing comma + } + mLengths = std::move(temp.mLengths); + return NS_OK; +} + +bool SVGLengthList::operator==(const SVGLengthList& rhs) const { + if (Length() != rhs.Length()) { + return false; + } + for (uint32_t i = 0; i < Length(); ++i) { + if (!(mLengths[i] == rhs.mLengths[i])) { + return false; + } + } + return true; +} + +} // namespace mozilla diff --git a/dom/svg/SVGLengthList.h b/dom/svg/SVGLengthList.h new file mode 100644 index 0000000000..9e3b7b8517 --- /dev/null +++ b/dom/svg/SVGLengthList.h @@ -0,0 +1,339 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGLENGTHLIST_H_ +#define DOM_SVG_SVGLENGTHLIST_H_ + +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsIContent.h" +#include "nsINode.h" +#include "nsIWeakReferenceUtils.h" +#include "SVGElement.h" +#include "nsTArray.h" +#include "SVGLength.h" +#include "mozilla/dom/SVGLengthBinding.h" + +namespace mozilla { + +namespace dom { +class DOMSVGLength; +class DOMSVGLengthList; +} // namespace dom + +/** + * ATTENTION! WARNING! WATCH OUT!! + * + * Consumers that modify objects of this type absolutely MUST keep the DOM + * wrappers for those lists (if any) in sync!! That's why this class is so + * locked down. + * + * The DOM wrapper class for this class is DOMSVGLengthList. + */ +class SVGLengthList { + friend class dom::DOMSVGLength; + friend class dom::DOMSVGLengthList; + friend class SVGAnimatedLengthList; + + public: + SVGLengthList() = default; + ~SVGLengthList() = default; + + SVGLengthList& operator=(const SVGLengthList& aOther) { + mLengths.ClearAndRetainStorage(); + // Best-effort, really. + Unused << mLengths.AppendElements(aOther.mLengths, fallible); + return *this; + } + + SVGLengthList(const SVGLengthList& aOther) { *this = aOther; } + + // Only methods that don't make/permit modification to this list are public. + // Only our friend classes can access methods that may change us. + + /// This may return an incomplete string on OOM, but that's acceptable. + void GetValueAsString(nsAString& aValue) const; + + bool IsEmpty() const { return mLengths.IsEmpty(); } + + uint32_t Length() const { return mLengths.Length(); } + + const SVGLength& operator[](uint32_t aIndex) const { + return mLengths[aIndex]; + } + + bool operator==(const SVGLengthList& rhs) const; + + bool SetCapacity(uint32_t size) { + return mLengths.SetCapacity(size, fallible); + } + + void Compact() { mLengths.Compact(); } + + // Access to methods that can modify objects of this type is deliberately + // limited. This is to reduce the chances of someone modifying objects of + // this type without taking the necessary steps to keep DOM wrappers in sync. + // If you need wider access to these methods, consider adding a method to + // SVGAnimatedLengthList and having that class act as an intermediary so it + // can take care of keeping DOM wrappers in sync. + + protected: + /** + * This may fail on OOM if the internal capacity needs to be increased, in + * which case the list will be left unmodified. + */ + nsresult CopyFrom(const SVGLengthList&); + void SwapWith(SVGLengthList& aOther) { + mLengths.SwapElements(aOther.mLengths); + } + + SVGLength& operator[](uint32_t aIndex) { return mLengths[aIndex]; } + + /** + * This may fail (return false) on OOM if the internal capacity is being + * increased, in which case the list will be left unmodified. + */ + bool SetLength(uint32_t aNumberOfItems) { + return mLengths.SetLength(aNumberOfItems, fallible); + } + + private: + // Marking the following private only serves to show which methods are only + // used by our friend classes (as opposed to our subclasses) - it doesn't + // really provide additional safety. + + nsresult SetValueFromString(const nsAString& aValue); + + void Clear() { mLengths.Clear(); } + + bool InsertItem(uint32_t aIndex, const SVGLength& aLength) { + if (aIndex >= mLengths.Length()) aIndex = mLengths.Length(); + return !!mLengths.InsertElementAt(aIndex, aLength, fallible); + } + + void ReplaceItem(uint32_t aIndex, const SVGLength& aLength) { + MOZ_ASSERT(aIndex < mLengths.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mLengths[aIndex] = aLength; + } + + void RemoveItem(uint32_t aIndex) { + MOZ_ASSERT(aIndex < mLengths.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mLengths.RemoveElementAt(aIndex); + } + + bool AppendItem(SVGLength aLength) { + return !!mLengths.AppendElement(aLength, fallible); + } + + protected: + /* Rationale for using nsTArray and not nsTArray: + * + * It might seem like we should use AutoTArray instead of + * nsTArray. That would preallocate space for one SVGLength and + * avoid an extra memory allocation call in the common case of the 'x' + * and 'y' attributes each containing a single length (and the 'dx' and 'dy' + * attributes being empty). However, consider this: + * + * An empty nsTArray uses sizeof(Header*). An AutoTArray on the other hand uses sizeof(Header*) + + * (2 * sizeof(uint32_t)) + (N * sizeof(E)), which for one SVGLength is + * sizeof(Header*) + 16 bytes. + * + * Now consider that for text elements we have four length list attributes + * (x, y, dx, dy), each of which can have a baseVal and an animVal list. If + * we were to go the AutoTArray route for each of these, we'd + * end up using at least 160 bytes for these four attributes alone, even + * though we only need storage for two SVGLengths (16 bytes) in the common + * case!! + * + * A compromise might be to template SVGLengthList to allow + * SVGAnimatedLengthList to preallocate space for an SVGLength for the + * baseVal lists only, and that would cut the space used by the four + * attributes to 96 bytes. Taking that even further and templating + * SVGAnimatedLengthList too in order to only use nsTArray for 'dx' and 'dy' + * would reduce the storage further to 64 bytes. Having different types makes + * things more complicated for code that needs to look at the lists though. + * In fact it also makes things more complicated when it comes to storing the + * lists. + * + * It may be worth considering using nsAttrValue for length lists instead of + * storing them directly on the element. + */ + FallibleTArray mLengths; +}; + +/** + * This SVGLengthList subclass is for SVGLengthListSMILType which needs to know + * which element and attribute a length list belongs to so that it can convert + * between unit types if necessary. + */ +class SVGLengthListAndInfo : public SVGLengthList { + public: + SVGLengthListAndInfo() + : mElement(nullptr), mAxis(0), mCanZeroPadList(false) {} + + SVGLengthListAndInfo(dom::SVGElement* aElement, uint8_t aAxis, + bool aCanZeroPadList) + : mElement(do_GetWeakReference(static_cast(aElement))), + mAxis(aAxis), + mCanZeroPadList(aCanZeroPadList) {} + + void SetInfo(dom::SVGElement* aElement, uint8_t aAxis, bool aCanZeroPadList) { + mElement = do_GetWeakReference(static_cast(aElement)); + mAxis = aAxis; + mCanZeroPadList = aCanZeroPadList; + } + + dom::SVGElement* Element() const { + nsCOMPtr e = do_QueryReferent(mElement); + return static_cast(e.get()); + } + + /** + * Returns true if this object is an "identity" value, from the perspective + * of SMIL. In other words, returns true until the initial value set up in + * SVGLengthListSMILType::Init() has been changed with a SetInfo() call. + */ + bool IsIdentity() const { + if (!mElement) { + MOZ_ASSERT(IsEmpty(), "target element propagation failure"); + return true; + } + return false; + } + + uint8_t Axis() const { + MOZ_ASSERT(mElement, "Axis() isn't valid"); + return mAxis; + } + + /** + * The value returned by this function depends on which attribute this object + * is for. If appending a list of zeros to the attribute's list would have no + * effect on rendering (e.g. the attributes 'dx' and 'dy' on ), then + * this method will return true. If appending a list of zeros to the + * attribute's list could *change* rendering (e.g. the attributes 'x' and 'y' + * on ), then this method will return false. + * + * The reason that this method exists is because the SMIL code needs to know + * what to do when it's asked to animate between lists of different length. + * If this method returns true, then it can zero pad the short list before + * carrying out its operations. However, in the case of the 'x' and 'y' + * attributes on , zero would mean "zero in the current coordinate + * system", whereas we would want to pad shorter lists with the coordinates + * at which glyphs would otherwise lie, which is almost certainly not zero! + * Animating from/to zeros in this case would produce terrible results. + * + * Currently SVGLengthListSMILType simply disallows (drops) animation between + * lists of different length if it can't zero pad a list. This is to avoid + * having some authors create content that depends on undesirable behaviour + * (which would make it difficult for us to fix the behavior in future). At + * some point it would be nice to implement a callback to allow this code to + * determine padding values for lists that can't be zero padded. See + * https://bugzilla.mozilla.org/show_bug.cgi?id=573431 + */ + bool CanZeroPadList() const { + // NS_ASSERTION(mElement, "CanZeroPadList() isn't valid"); + return mCanZeroPadList; + } + + // For the SMIL code. See comment in SVGLengthListSMILType::Add(). + void SetCanZeroPadList(bool aCanZeroPadList) { + mCanZeroPadList = aCanZeroPadList; + } + + nsresult CopyFrom(const SVGLengthListAndInfo& rhs) { + mElement = rhs.mElement; + mAxis = rhs.mAxis; + mCanZeroPadList = rhs.mCanZeroPadList; + return SVGLengthList::CopyFrom(rhs); + } + + // Instances of this special subclass do not have DOM wrappers that we need + // to worry about keeping in sync, so it's safe to expose any hidden base + // class methods required by the SMIL code, as we do below. + + /** + * Exposed so that SVGLengthList baseVals can be copied to + * SVGLengthListAndInfo objects. Note that callers should also call + * SetInfo() when using this method! + */ + nsresult CopyFrom(const SVGLengthList& rhs) { + return SVGLengthList::CopyFrom(rhs); + } + const SVGLength& operator[](uint32_t aIndex) const { + return SVGLengthList::operator[](aIndex); + } + SVGLength& operator[](uint32_t aIndex) { + return SVGLengthList::operator[](aIndex); + } + bool SetLength(uint32_t aNumberOfItems) { + return SVGLengthList::SetLength(aNumberOfItems); + } + + private: + // We must keep a weak reference to our element because we may belong to a + // cached baseVal SMILValue. See the comments starting at: + // https://bugzilla.mozilla.org/show_bug.cgi?id=515116#c15 + // See also https://bugzilla.mozilla.org/show_bug.cgi?id=653497 + nsWeakPtr mElement; + uint8_t mAxis; + bool mCanZeroPadList; +}; + +/** + * This class wraps SVGLengthList objects to allow frame consumers to process + * SVGLengthList objects as if they were simply a list of float values in user + * units. When consumers request the value at a given index, this class + * dynamically converts the corresponding SVGLength from its actual unit and + * returns its value in user units. + * + * Consumers should check that the user unit values returned are finite. Even + * if the consumer can guarantee the list's element has a valid viewport + * ancestor to resolve percentage units against, and a valid presContext and + * ComputedStyle to resolve absolute and em/ex units against, unit conversions + * could still overflow. In that case the value returned will be + * numeric_limits::quiet_NaN(). + */ +class MOZ_STACK_CLASS SVGUserUnitList { + public: + SVGUserUnitList() : mList(nullptr), mElement(nullptr), mAxis(0) {} + + void Init(const SVGLengthList* aList, dom::SVGElement* aElement, + uint8_t aAxis) { + mList = aList; + mElement = aElement; + mAxis = aAxis; + } + + void Clear() { mList = nullptr; } + + bool IsEmpty() const { return !mList || mList->IsEmpty(); } + + uint32_t Length() const { return mList ? mList->Length() : 0; } + + /// This may return a non-finite value + float operator[](uint32_t aIndex) const { + return (*mList)[aIndex].GetValueInUserUnits(mElement, mAxis); + } + + bool HasPercentageValueAt(uint32_t aIndex) const { + const SVGLength& length = (*mList)[aIndex]; + return length.GetUnit() == + dom::SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE; + } + + private: + const SVGLengthList* mList; + dom::SVGElement* mElement; + uint8_t mAxis; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGLENGTHLIST_H_ diff --git a/dom/svg/SVGLengthListSMILType.cpp b/dom/svg/SVGLengthListSMILType.cpp new file mode 100644 index 0000000000..940e2d636c --- /dev/null +++ b/dom/svg/SVGLengthListSMILType.cpp @@ -0,0 +1,290 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGLengthListSMILType.h" + +#include "mozilla/FloatingPoint.h" +#include "mozilla/SMILValue.h" +#include "nsMathUtils.h" +#include "SVGLengthList.h" +#include +#include + +namespace mozilla { + +/*static*/ +SVGLengthListSMILType SVGLengthListSMILType::sSingleton; + +//---------------------------------------------------------------------- +// nsISMILType implementation + +void SVGLengthListSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + + SVGLengthListAndInfo* lengthList = new SVGLengthListAndInfo(); + + // See the comment documenting Init() in our header file: + lengthList->SetCanZeroPadList(true); + + aValue.mU.mPtr = lengthList; + aValue.mType = this; +} + +void SVGLengthListSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value type"); + delete static_cast(aValue.mU.mPtr); + aValue.mU.mPtr = nullptr; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGLengthListSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value"); + + const SVGLengthListAndInfo* src = + static_cast(aSrc.mU.mPtr); + SVGLengthListAndInfo* dest = + static_cast(aDest.mU.mPtr); + + return dest->CopyFrom(*src); +} + +bool SVGLengthListSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected type for SMIL value"); + + return *static_cast(aLeft.mU.mPtr) == + *static_cast(aRight.mU.mPtr); +} + +nsresult SVGLengthListSMILType::Add(SMILValue& aDest, + const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aValueToAdd.mType == this, "Incompatible SMIL type"); + + SVGLengthListAndInfo& dest = + *static_cast(aDest.mU.mPtr); + const SVGLengthListAndInfo& valueToAdd = + *static_cast(aValueToAdd.mU.mPtr); + + // To understand this code, see the comments documenting our Init() method, + // and documenting SVGLengthListAndInfo::CanZeroPadList(). + + // Note that *this* method actually may safely zero pad a shorter list + // regardless of the value returned by CanZeroPadList() for that list, + // just so long as the shorter list is being added *to* the longer list + // and *not* vice versa! It's okay in the case of adding a shorter list to a + // longer list because during the add operation we'll end up adding the + // zeros to actual specified values. It's *not* okay in the case of adding a + // longer list to a shorter list because then we end up adding to implicit + // zeros when we'd actually need to add to whatever the underlying values + // should be, not zeros, and those values are not explicit or otherwise + // available. + + if (valueToAdd.IsIdentity()) { // Adding identity value - no-op + return NS_OK; + } + + if (dest.IsIdentity()) { // Adding *to* an identity value + if (!dest.SetLength(valueToAdd.Length())) { + return NS_ERROR_OUT_OF_MEMORY; + } + for (uint32_t i = 0; i < dest.Length(); ++i) { + dest[i].SetValueAndUnit(valueToAdd[i].GetValueInCurrentUnits() * aCount, + valueToAdd[i].GetUnit()); + } + dest.SetInfo( + valueToAdd.Element(), valueToAdd.Axis(), + valueToAdd.CanZeroPadList()); // propagate target element info! + return NS_OK; + } + MOZ_ASSERT(dest.Element() == valueToAdd.Element(), + "adding values from different elements...?"); + + // Zero-pad our |dest| list, if necessary. + if (dest.Length() < valueToAdd.Length()) { + if (!dest.CanZeroPadList()) { + // SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(valueToAdd.CanZeroPadList(), + "values disagree about attribute's zero-paddibility"); + + uint32_t i = dest.Length(); + if (!dest.SetLength(valueToAdd.Length())) { + return NS_ERROR_OUT_OF_MEMORY; + } + for (; i < valueToAdd.Length(); ++i) { + dest[i].SetValueAndUnit(0.0f, valueToAdd[i].GetUnit()); + } + } + + for (uint32_t i = 0; i < valueToAdd.Length(); ++i) { + float valToAdd; + if (dest[i].GetUnit() == valueToAdd[i].GetUnit()) { + valToAdd = valueToAdd[i].GetValueInCurrentUnits(); + } else { + // If units differ, we use the unit of the item in 'dest'. + // We leave it to the frame code to check that values are finite. + valToAdd = valueToAdd[i].GetValueInSpecifiedUnit( + dest[i].GetUnit(), dest.Element(), dest.Axis()); + } + dest[i].SetValueAndUnit( + dest[i].GetValueInCurrentUnits() + valToAdd * aCount, + dest[i].GetUnit()); + } + + // propagate target element info! + dest.SetInfo(valueToAdd.Element(), valueToAdd.Axis(), + dest.CanZeroPadList() && valueToAdd.CanZeroPadList()); + + return NS_OK; +} + +nsresult SVGLengthListSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aTo.mType == this, "Incompatible SMIL type"); + + const SVGLengthListAndInfo& from = + *static_cast(aFrom.mU.mPtr); + const SVGLengthListAndInfo& to = + *static_cast(aTo.mU.mPtr); + + // To understand this code, see the comments documenting our Init() method, + // and documenting SVGLengthListAndInfo::CanZeroPadList(). + + NS_ASSERTION((from.CanZeroPadList() == to.CanZeroPadList()) || + (from.CanZeroPadList() && from.IsEmpty()) || + (to.CanZeroPadList() && to.IsEmpty()), + "Only \"zero\" SMILValues from the SMIL engine should " + "return true for CanZeroPadList() when the attribute " + "being animated can't be zero padded"); + + if ((from.Length() < to.Length() && !from.CanZeroPadList()) || + (to.Length() < from.Length() && !to.CanZeroPadList())) { + // SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + + // We return the root of the sum of the squares of the deltas between the + // user unit values of the lengths at each correspanding index. In the + // general case, paced animation is probably not useful, but this strategy at + // least does the right thing for paced animation in the face of simple + // 'values' lists such as: + // + // values="100 200 300; 101 201 301; 110 210 310" + // + // I.e. half way through the simple duration we'll get "105 205 305". + + double total = 0.0; + + uint32_t i = 0; + for (; i < from.Length() && i < to.Length(); ++i) { + double f = from[i].GetValueInUserUnits(from.Element(), from.Axis()); + double t = to[i].GetValueInUserUnits(to.Element(), to.Axis()); + double delta = t - f; + total += delta * delta; + } + + // In the case that from.Length() != to.Length(), one of the following loops + // will run. (OK since CanZeroPadList()==true for the other list.) + + for (; i < from.Length(); ++i) { + double f = from[i].GetValueInUserUnits(from.Element(), from.Axis()); + total += f * f; + } + for (; i < to.Length(); ++i) { + double t = to[i].GetValueInUserUnits(to.Element(), to.Axis()); + total += t * t; + } + + float distance = sqrt(total); + if (!std::isfinite(distance)) { + return NS_ERROR_FAILURE; + } + aDistance = distance; + return NS_OK; +} + +nsresult SVGLengthListSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + + const SVGLengthListAndInfo& start = + *static_cast(aStartVal.mU.mPtr); + const SVGLengthListAndInfo& end = + *static_cast(aEndVal.mU.mPtr); + SVGLengthListAndInfo& result = + *static_cast(aResult.mU.mPtr); + + // To understand this code, see the comments documenting our Init() method, + // and documenting SVGLengthListAndInfo::CanZeroPadList(). + + NS_ASSERTION((start.CanZeroPadList() == end.CanZeroPadList()) || + (start.CanZeroPadList() && start.IsEmpty()) || + (end.CanZeroPadList() && end.IsEmpty()), + "Only \"zero\" SMILValues from the SMIL engine should " + "return true for CanZeroPadList() when the attribute " + "being animated can't be zero padded"); + + if ((start.Length() < end.Length() && !start.CanZeroPadList()) || + (end.Length() < start.Length() && !end.CanZeroPadList())) { + // SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + + if (!result.SetLength(std::max(start.Length(), end.Length()))) { + return NS_ERROR_OUT_OF_MEMORY; + } + + uint32_t i = 0; + for (; i < start.Length() && i < end.Length(); ++i) { + float s; + if (start[i].GetUnit() == end[i].GetUnit()) { + s = start[i].GetValueInCurrentUnits(); + } else { + // If units differ, we use the unit of the item in 'end'. + // We leave it to the frame code to check that values are finite. + s = start[i].GetValueInSpecifiedUnit(end[i].GetUnit(), end.Element(), + end.Axis()); + } + float e = end[i].GetValueInCurrentUnits(); + result[i].SetValueAndUnit(s + (e - s) * aUnitDistance, end[i].GetUnit()); + } + + // In the case that start.Length() != end.Length(), one of the following + // loops will run. (Okay, since CanZeroPadList()==true for the other list.) + + for (; i < start.Length(); ++i) { + result[i].SetValueAndUnit( + start[i].GetValueInCurrentUnits() - + start[i].GetValueInCurrentUnits() * aUnitDistance, + start[i].GetUnit()); + } + for (; i < end.Length(); ++i) { + result[i].SetValueAndUnit(end[i].GetValueInCurrentUnits() * aUnitDistance, + end[i].GetUnit()); + } + + // propagate target element info! + result.SetInfo(end.Element(), end.Axis(), + start.CanZeroPadList() && end.CanZeroPadList()); + + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGLengthListSMILType.h b/dom/svg/SVGLengthListSMILType.h new file mode 100644 index 0000000000..ebfccdb85f --- /dev/null +++ b/dom/svg/SVGLengthListSMILType.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGLENGTHLISTSMILTYPE_H_ +#define DOM_SVG_SVGLENGTHLISTSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +namespace mozilla { + +class SMILValue; + +//////////////////////////////////////////////////////////////////////// +// SVGLengthListSMILType +// +// Operations for animating an SVGLengthList. +// +class SVGLengthListSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGLengthListSMILType sSingleton; + + protected: + // SMILType Methods + // ------------------- + + /** + * When this method initializes the SVGLengthListAndInfo for its SMILValue + * argument, it has to blindly set its mCanZeroPadList to true despite + * the fact that some attributes can't be zero-padded. (See the explaination + * that follows.) SVGAnimatedLengthList::SMILAnimatedLengthList's + * GetBaseValue() and ValueFromString() methods then override this for the + * SMILValue objects that they create to set this flag to the appropriate + * value for the attribute in question. + * + * The reason that we default to setting the mCanZeroPadList to true is + * because the SMIL engine creates "zero" valued SMILValue objects for + * intermediary calculations, and may pass such a SMILValue (along with a + * SMILValue from an animation element - that is a SMILValue created by + * SVGAnimatedLengthList::SMILAnimatedLengthList's GetBaseValue() or + * ValueFromString() methods) into the Add(), ComputeDistance() or + * Interpolate() methods below. Even in the case of animation of list + * attributes that may *not* be padded with zeros (such as 'x' and 'y' on the + * element), we need to allow zero-padding of these "zero" valued + * SMILValue's lists. One reason for this is illustrated by the following + * example: + * + * foo + * + * + * + * In this example there are two SMIL animation layers to be sandwiched: the + * base layer, and the layer created for the element. The SMIL + * engine calculates the result of each layer *independently*, before + * compositing the results together. Thus for the sandwich layer + * the SMIL engine interpolates between a "zero" SMILValue that it creates + * (since there is no explicit "from") and the "2 2", before the result of + * that interpolation is added to the "2 4" from the base layer. Clearly for + * the interpolation between the "zero" SMILValue and "2 2" to work, the + * "zero" SMILValue's SVGLengthListAndInfo must be zero paddable - hence + * why this method always sets mCanZeroPadList to true. + * + * (Since the Add(), ComputeDistance() and Interpolate() methods may be + * passed two input SMILValue objects for which CanZeroPadList() returns + * opposite values, these methods must be careful what they set the flag to + * on the SMILValue that they output. If *either* of the input SMILValues + * has an SVGLengthListAndInfo for which CanZeroPadList() returns false, + * then they must set the flag to false on the output SMILValue too. If + * the methods failed to do that, then when the result SMILValue objects + * from each sandwich layer are composited together, we could end up allowing + * animation between lists of different length when we should not!) + */ + void Init(SMILValue& aValue) const override; + + void Destroy(SMILValue& aValue) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGLengthListSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGLENGTHLISTSMILTYPE_H_ diff --git a/dom/svg/SVGLineElement.cpp b/dom/svg/SVGLineElement.cpp new file mode 100644 index 0000000000..7834bd14ad --- /dev/null +++ b/dom/svg/SVGLineElement.cpp @@ -0,0 +1,224 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGLineElement.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGLineElementBinding.h" +#include "mozilla/gfx/2D.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Line) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGLineElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGLineElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGLineElement::sLengthInfo[4] = { + {nsGkAtoms::x1, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::y1, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::x2, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::y2, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, +}; + +//---------------------------------------------------------------------- +// Implementation + +SVGLineElement::SVGLineElement( + already_AddRefed&& aNodeInfo) + : SVGLineElementBase(std::move(aNodeInfo)) {} + +void SVGLineElement::MaybeAdjustForZeroLength(float aX1, float aY1, float& aX2, + float aY2) { + if (aX1 == aX2 && aY1 == aY2) { + SVGContentUtils::AutoStrokeOptions strokeOptions; + SVGContentUtils::GetStrokeOptions(&strokeOptions, this, nullptr, nullptr, + SVGContentUtils::eIgnoreStrokeDashing); + + if (strokeOptions.mLineCap != CapStyle::BUTT) { + float tinyLength = + strokeOptions.mLineWidth / SVG_ZERO_LENGTH_PATH_FIX_FACTOR; + aX2 += tinyLength; + } + } +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGLineElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGLineElement::X1() { + return mLengthAttributes[ATTR_X1].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGLineElement::Y1() { + return mLengthAttributes[ATTR_Y1].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGLineElement::X2() { + return mLengthAttributes[ATTR_X2].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGLineElement::Y2() { + return mLengthAttributes[ATTR_Y2].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::LengthAttributesInfo SVGLineElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +void SVGLineElement::GetMarkPoints(nsTArray* aMarks) { + float x1, y1, x2, y2; + + GetAnimatedLengthValues(&x1, &y1, &x2, &y2, nullptr); + + float angle = std::atan2(y2 - y1, x2 - x1); + + aMarks->AppendElement(SVGMark(x1, y1, angle, SVGMark::eStart)); + aMarks->AppendElement(SVGMark(x2, y2, angle, SVGMark::eEnd)); +} + +void SVGLineElement::GetAsSimplePath(SimplePath* aSimplePath) { + float x1, y1, x2, y2; + GetAnimatedLengthValues(&x1, &y1, &x2, &y2, nullptr); + + MaybeAdjustForZeroLength(x1, y1, x2, y2); + aSimplePath->SetLine(x1, y1, x2, y2); +} + +already_AddRefed SVGLineElement::BuildPath(PathBuilder* aBuilder) { + float x1, y1, x2, y2; + GetAnimatedLengthValues(&x1, &y1, &x2, &y2, nullptr); + + MaybeAdjustForZeroLength(x1, y1, x2, y2); + aBuilder->MoveTo(Point(x1, y1)); + aBuilder->LineTo(Point(x2, y2)); + + return aBuilder->Finish(); +} + +bool SVGLineElement::GetGeometryBounds(Rect* aBounds, + const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace) { + float x1, y1, x2, y2; + GetAnimatedLengthValues(&x1, &y1, &x2, &y2, nullptr); + + if (aStrokeOptions.mLineWidth <= 0) { + *aBounds = Rect(aToBoundsSpace.TransformPoint(Point(x1, y1)), Size()); + aBounds->ExpandToEnclose(aToBoundsSpace.TransformPoint(Point(x2, y2))); + return true; + } + + // transform from non-scaling-stroke space to the space in which we compute + // bounds + Matrix nonScalingToBounds; + if (aToNonScalingStrokeSpace) { + MOZ_ASSERT(!aToNonScalingStrokeSpace->IsSingular()); + Matrix nonScalingToUser = aToNonScalingStrokeSpace->Inverse(); + nonScalingToBounds = nonScalingToUser * aToBoundsSpace; + } + + if (aStrokeOptions.mLineCap == CapStyle::ROUND) { + if (!aToBoundsSpace.IsRectilinear() || + (aToNonScalingStrokeSpace && + !aToNonScalingStrokeSpace->IsRectilinear())) { + // TODO: handle this case. + return false; + } + Rect bounds(Point(x1, y1), Size()); + bounds.ExpandToEnclose(Point(x2, y2)); + if (aToNonScalingStrokeSpace) { + bounds = aToNonScalingStrokeSpace->TransformBounds(bounds); + bounds.Inflate(aStrokeOptions.mLineWidth / 2.f); + *aBounds = nonScalingToBounds.TransformBounds(bounds); + } else { + bounds.Inflate(aStrokeOptions.mLineWidth / 2.f); + *aBounds = aToBoundsSpace.TransformBounds(bounds); + } + return true; + } + + // Handle butt and square linecap, normal and non-scaling stroke cases + // together: start with endpoints (x1, y1), (x2, y2) in the stroke space, + // compute the four corners of the stroked line, transform the corners to + // bounds space, and compute bounds there. + + if (aToNonScalingStrokeSpace) { + Point nonScalingSpaceP1, nonScalingSpaceP2; + nonScalingSpaceP1 = aToNonScalingStrokeSpace->TransformPoint(Point(x1, y1)); + nonScalingSpaceP2 = aToNonScalingStrokeSpace->TransformPoint(Point(x2, y2)); + x1 = nonScalingSpaceP1.x; + y1 = nonScalingSpaceP1.y; + x2 = nonScalingSpaceP2.x; + y2 = nonScalingSpaceP2.y; + } + + Float length = Float(NS_hypot(x2 - x1, y2 - y1)); + Float xDelta; + Float yDelta; + Point points[4]; + + if (aStrokeOptions.mLineCap == CapStyle::BUTT) { + if (length == 0.f) { + xDelta = yDelta = 0.f; + } else { + Float ratio = aStrokeOptions.mLineWidth / 2.f / length; + xDelta = ratio * (y2 - y1); + yDelta = ratio * (x2 - x1); + } + points[0] = Point(x1 - xDelta, y1 + yDelta); + points[1] = Point(x1 + xDelta, y1 - yDelta); + points[2] = Point(x2 + xDelta, y2 - yDelta); + points[3] = Point(x2 - xDelta, y2 + yDelta); + } else { + MOZ_ASSERT(aStrokeOptions.mLineCap == CapStyle::SQUARE); + if (length == 0.f) { + xDelta = yDelta = aStrokeOptions.mLineWidth / 2.f; + points[0] = Point(x1 - xDelta, y1 + yDelta); + points[1] = Point(x1 - xDelta, y1 - yDelta); + points[2] = Point(x1 + xDelta, y1 - yDelta); + points[3] = Point(x1 + xDelta, y1 + yDelta); + } else { + Float ratio = aStrokeOptions.mLineWidth / 2.f / length; + yDelta = ratio * (x2 - x1); + xDelta = ratio * (y2 - y1); + points[0] = Point(x1 - yDelta - xDelta, y1 - xDelta + yDelta); + points[1] = Point(x1 - yDelta + xDelta, y1 - xDelta - yDelta); + points[2] = Point(x2 + yDelta + xDelta, y2 + xDelta - yDelta); + points[3] = Point(x2 + yDelta - xDelta, y2 + xDelta + yDelta); + } + } + + const Matrix& toBoundsSpace = + aToNonScalingStrokeSpace ? nonScalingToBounds : aToBoundsSpace; + + *aBounds = Rect(toBoundsSpace.TransformPoint(points[0]), Size()); + for (uint32_t i = 1; i < 4; ++i) { + aBounds->ExpandToEnclose(toBoundsSpace.TransformPoint(points[i])); + } + + return true; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGLineElement.h b/dom/svg/SVGLineElement.h new file mode 100644 index 0000000000..38b6810fc2 --- /dev/null +++ b/dom/svg/SVGLineElement.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGLINEELEMENT_H_ +#define DOM_SVG_SVGLINEELEMENT_H_ + +#include "SVGAnimatedLength.h" +#include "SVGGeometryElement.h" + +nsresult NS_NewSVGLineElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGLineElementBase = SVGGeometryElement; + +class SVGLineElement final : public SVGLineElementBase { + protected: + explicit SVGLineElement(already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + friend nsresult(::NS_NewSVGLineElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + // If the input line has length zero and linecaps aren't butt, adjust |aX2| by + // a tiny amount to a barely-nonzero-length line that all of our draw targets + // will render + void MaybeAdjustForZeroLength(float aX1, float aY1, float& aX2, float aY2); + + public: + // SVGGeometryElement methods: + bool IsMarkable() override { return true; } + void GetMarkPoints(nsTArray* aMarks) override; + void GetAsSimplePath(SimplePath* aSimplePath) override; + already_AddRefed BuildPath(PathBuilder* aBuilder) override; + bool GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed X1(); + already_AddRefed Y1(); + already_AddRefed X2(); + already_AddRefed Y2(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + + enum { ATTR_X1, ATTR_Y1, ATTR_X2, ATTR_Y2 }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGLINEELEMENT_H_ diff --git a/dom/svg/SVGMPathElement.cpp b/dom/svg/SVGMPathElement.cpp new file mode 100644 index 0000000000..ec441b9aeb --- /dev/null +++ b/dom/svg/SVGMPathElement.cpp @@ -0,0 +1,240 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGMPathElement.h" + +#include "nsDebug.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/SVGAnimateMotionElement.h" +#include "mozilla/dom/SVGGeometryElement.h" +#include "nsContentUtils.h" +#include "nsIReferrerInfo.h" +#include "mozilla/dom/SVGMPathElementBinding.h" +#include "nsIURI.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(MPath) + +namespace mozilla::dom { + +JSObject* SVGMPathElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGMPathElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGMPathElement::sStringInfo[2] = { + {nsGkAtoms::href, kNameSpaceID_None, false}, + {nsGkAtoms::href, kNameSpaceID_XLink, false}}; + +// Cycle collection magic -- based on SVGUseElement +NS_IMPL_CYCLE_COLLECTION_CLASS(SVGMPathElement) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGMPathElement, + SVGMPathElementBase) + tmp->UnlinkHrefTarget(false); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGMPathElement, + SVGMPathElementBase) + tmp->mPathTracker.Traverse(&cb); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(SVGMPathElement, + SVGMPathElementBase, + nsIMutationObserver) + +// Constructor +SVGMPathElement::SVGMPathElement( + already_AddRefed&& aNodeInfo) + : SVGMPathElementBase(std::move(aNodeInfo)), mPathTracker(this) {} + +SVGMPathElement::~SVGMPathElement() { UnlinkHrefTarget(false); } + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGMPathElement) + +already_AddRefed SVGMPathElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +nsresult SVGMPathElement::BindToTree(BindContext& aContext, nsINode& aParent) { + MOZ_ASSERT(!mPathTracker.get(), + "Shouldn't have href-target yet (or it should've been cleared)"); + nsresult rv = SVGMPathElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + if (IsInComposedDoc()) { + const nsAttrValue* hrefAttrValue = + HasAttr(kNameSpaceID_None, nsGkAtoms::href) + ? mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_None) + : mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink); + if (hrefAttrValue) { + UpdateHrefTarget(nsIContent::FromNode(aParent), + hrefAttrValue->GetStringValue()); + } + } + + return NS_OK; +} + +void SVGMPathElement::UnbindFromTree(bool aNullParent) { + UnlinkHrefTarget(true); + SVGMPathElementBase::UnbindFromTree(aNullParent); +} + +void SVGMPathElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aMaybeScriptedPrincipal, + bool aNotify) { + if (aName == nsGkAtoms::href) { + if (aValue) { + if ((aNamespaceID == kNameSpaceID_XLink || + aNamespaceID == kNameSpaceID_None) && + IsInComposedDoc()) { + // Note: If we fail the IsInComposedDoc call, it's ok -- we'll update + // the target on next BindToTree call. + + // Note: "href" takes priority over xlink:href. So if "xlink:href" is + // being set here, we only let that update our target if "href" is + // *unset*. + if (aNamespaceID != kNameSpaceID_XLink || + !mStringAttributes[HREF].IsExplicitlySet()) { + UpdateHrefTarget(GetParent(), aValue->GetStringValue()); + } + } + } else { + // href attr being removed. + if (aNamespaceID == kNameSpaceID_None) { + UnlinkHrefTarget(true); + + // After unsetting href, we may still have xlink:href, so we should + // try to add it back. + const nsAttrValue* xlinkHref = + mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink); + if (xlinkHref) { + UpdateHrefTarget(GetParent(), xlinkHref->GetStringValue()); + } + } else if (aNamespaceID == kNameSpaceID_XLink && + !HasAttr(nsGkAtoms::href)) { + UnlinkHrefTarget(true); + } // else: we unset some random-namespace href attribute, or unset + // xlink:href but still have href attribute, so keep the target linking + // to href. + } + } + + return SVGMPathElementBase::AfterSetAttr( + aNamespaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::StringAttributesInfo SVGMPathElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +//---------------------------------------------------------------------- +// nsIMutationObserver methods + +void SVGMPathElement::AttributeChanged(Element* aElement, int32_t aNameSpaceID, + nsAtom* aAttribute, int32_t aModType, + const nsAttrValue* aOldValue) { + if (aNameSpaceID == kNameSpaceID_None) { + if (aAttribute == nsGkAtoms::d) { + NotifyParentOfMpathChange(GetParent()); + } + } +} + +//---------------------------------------------------------------------- +// Public helper methods + +SVGGeometryElement* SVGMPathElement::GetReferencedPath() { + if (!HasAttr(kNameSpaceID_XLink, nsGkAtoms::href) && + !HasAttr(kNameSpaceID_None, nsGkAtoms::href)) { + MOZ_ASSERT(!mPathTracker.get(), + "We shouldn't have a href target " + "if we don't have an xlink:href or href attribute"); + return nullptr; + } + + return SVGGeometryElement::FromNodeOrNull(mPathTracker.get()); +} + +//---------------------------------------------------------------------- +// Protected helper methods + +void SVGMPathElement::UpdateHrefTarget(nsIContent* aParent, + const nsAString& aHrefStr) { + nsCOMPtr baseURI = GetBaseURI(); + if (nsContentUtils::IsLocalRefURL(aHrefStr)) { + baseURI = SVGObserverUtils::GetBaseURLForLocalRef(this, baseURI); + } + nsCOMPtr targetURI; + nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), aHrefStr, + OwnerDoc(), baseURI); + + // Stop observing old target (if any) + if (mPathTracker.get()) { + mPathTracker.get()->RemoveMutationObserver(this); + } + + if (aParent) { + // Pass in |aParent| instead of |this| -- first argument is only used + // for a call to GetComposedDoc(), and |this| might not have a current + // document yet (if our caller is BindToTree). + nsCOMPtr referrerInfo = + OwnerDoc()->ReferrerInfoForInternalCSSAndSVGResources(); + mPathTracker.ResetToURIFragmentID(aParent, targetURI, referrerInfo); + } else { + // if we don't have a parent, then there's no animateMotion element + // depending on our target, so there's no point tracking it right now. + mPathTracker.Unlink(); + } + + // Start observing new target (if any) + if (mPathTracker.get()) { + mPathTracker.get()->AddMutationObserver(this); + } + + NotifyParentOfMpathChange(aParent); +} + +void SVGMPathElement::UnlinkHrefTarget(bool aNotifyParent) { + // Stop observing old target (if any) + if (mPathTracker.get()) { + mPathTracker.get()->RemoveMutationObserver(this); + } + mPathTracker.Unlink(); + + if (aNotifyParent) { + NotifyParentOfMpathChange(GetParent()); + } +} + +void SVGMPathElement::NotifyParentOfMpathChange(nsIContent* aParent) { + if (auto* animateMotionParent = + SVGAnimateMotionElement::FromNodeOrNull(aParent)) { + animateMotionParent->MpathChanged(); + AnimationNeedsResample(); + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGMPathElement.h b/dom/svg/SVGMPathElement.h new file mode 100644 index 0000000000..411d70a598 --- /dev/null +++ b/dom/svg/SVGMPathElement.h @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGMPATHELEMENT_H_ +#define DOM_SVG_SVGMPATHELEMENT_H_ + +#include "mozilla/dom/IDTracker.h" +#include "mozilla/dom/SVGElement.h" +#include "nsStubMutationObserver.h" +#include "SVGAnimatedString.h" + +nsresult NS_NewSVGMPathElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { +class SVGGeometryElement; + +using SVGMPathElementBase = SVGElement; + +class SVGMPathElement final : public SVGMPathElementBase, + public nsStubMutationObserver { + protected: + friend nsresult(::NS_NewSVGMPathElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGMPathElement( + already_AddRefed&& aNodeInfo); + ~SVGMPathElement(); + + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + // interfaces: + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SVGMPathElement, SVGMPathElementBase) + + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + + // nsIContent interface + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + nsresult BindToTree(BindContext&, nsINode& aParent) override; + void UnbindFromTree(bool aNullParent) override; + + // Element specializations + void AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, const nsAttrValue* aOldValue, + nsIPrincipal* aMaybeScriptedPrincipal, + bool aNotify) override; + + // Public helper method: If our xlink:href attribute links to a Shape + // element, this method returns a pointer to that element. Otherwise, + // this returns nullptr. + SVGGeometryElement* GetReferencedPath(); + + // WebIDL + already_AddRefed Href(); + + protected: + /** + * Helper that provides a reference to the 'path' element with the ID that is + * referenced by the 'mpath' element's 'href' attribute, and that will + * invalidate the parent of the 'mpath' and update mutation observers to the + * new path element if the element that that ID identifies changes to a + * different element (or none). + */ + class PathElementTracker final : public IDTracker { + public: + explicit PathElementTracker(SVGMPathElement* aMpathElement) + : mMpathElement(aMpathElement) {} + + protected: + // We need to be notified when target changes, in order to request a sample + // (which will clear animation effects that used the old target-path + // and recompute the animation effects using the new target-path). + void ElementChanged(Element* aFrom, Element* aTo) override { + IDTracker::ElementChanged(aFrom, aTo); + if (aFrom) { + aFrom->RemoveMutationObserver(mMpathElement); + } + if (aTo) { + aTo->AddMutationObserver(mMpathElement); + } + mMpathElement->NotifyParentOfMpathChange(mMpathElement->GetParent()); + } + + // We need to override IsPersistent to get persistent tracking (beyond the + // first time the target changes) + bool IsPersistent() override { return true; } + + private: + SVGMPathElement* const mMpathElement; + }; + + StringAttributesInfo GetStringInfo() override; + + void UpdateHrefTarget(nsIContent* aParent, const nsAString& aHrefStr); + void UnlinkHrefTarget(bool aNotifyParent); + void NotifyParentOfMpathChange(nsIContent* aParent); + + enum { HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; + PathElementTracker mPathTracker; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGMPATHELEMENT_H_ diff --git a/dom/svg/SVGMarkerElement.cpp b/dom/svg/SVGMarkerElement.cpp new file mode 100644 index 0000000000..5467c4ac11 --- /dev/null +++ b/dom/svg/SVGMarkerElement.cpp @@ -0,0 +1,218 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGMarkerElement.h" + +#include "nsGkAtoms.h" +#include "DOMSVGAngle.h" +#include "SVGAnimatedPreserveAspectRatio.h" +#include "nsError.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGGeometryElement.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGMarkerElementBinding.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/RefPtr.h" +#include "SVGContentUtils.h" + +using namespace mozilla::gfx; +using namespace mozilla::dom::SVGMarkerElement_Binding; + +NS_IMPL_NS_NEW_SVG_ELEMENT(Marker) + +namespace mozilla::dom { + +using namespace SVGAngle_Binding; + +JSObject* SVGMarkerElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGMarkerElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGMarkerElement::sLengthInfo[4] = { + {nsGkAtoms::refX, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::refY, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::markerWidth, 3, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::markerHeight, 3, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, +}; + +SVGEnumMapping SVGMarkerElement::sUnitsMap[] = { + {nsGkAtoms::strokeWidth, SVG_MARKERUNITS_STROKEWIDTH}, + {nsGkAtoms::userSpaceOnUse, SVG_MARKERUNITS_USERSPACEONUSE}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGMarkerElement::sEnumInfo[1] = { + {nsGkAtoms::markerUnits, sUnitsMap, SVG_MARKERUNITS_STROKEWIDTH}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGMarkerElement::SVGMarkerElement( + already_AddRefed&& aNodeInfo) + : SVGMarkerElementBase(std::move(aNodeInfo)), mCoordCtx(nullptr) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGMarkerElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGMarkerElement::ViewBox() { + return mViewBox.ToSVGAnimatedRect(this); +} + +already_AddRefed +SVGMarkerElement::PreserveAspectRatio() { + return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this); +} + +//---------------------------------------------------------------------- + +already_AddRefed SVGMarkerElement::RefX() { + return mLengthAttributes[REFX].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGMarkerElement::RefY() { + return mLengthAttributes[REFY].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGMarkerElement::MarkerUnits() { + return mEnumAttributes[MARKERUNITS].ToDOMAnimatedEnum(this); +} + +already_AddRefed SVGMarkerElement::MarkerWidth() { + return mLengthAttributes[MARKERWIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGMarkerElement::MarkerHeight() { + return mLengthAttributes[MARKERHEIGHT].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGMarkerElement::OrientType() { + return mOrient.ToDOMAnimatedEnum(this); +} + +already_AddRefed SVGMarkerElement::OrientAngle() { + return mOrient.ToDOMAnimatedAngle(this); +} + +void SVGMarkerElement::SetOrientToAuto() { + mOrient.SetBaseType(SVG_MARKER_ORIENT_AUTO, this, IgnoreErrors()); +} + +void SVGMarkerElement::SetOrientToAngle(DOMSVGAngle& aAngle) { + nsAutoString angle; + aAngle.GetValueAsString(angle); + mOrient.SetBaseValueString(angle, this, true); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +void SVGMarkerElement::SetParentCoordCtxProvider(SVGViewportElement* aContext) { + mCoordCtx = aContext; + mViewBoxToViewportTransform = nullptr; +} + +/* virtual */ +bool SVGMarkerElement::HasValidDimensions() const { + return (!mLengthAttributes[MARKERWIDTH].IsExplicitlySet() || + mLengthAttributes[MARKERWIDTH].GetAnimValInSpecifiedUnits() > 0) && + (!mLengthAttributes[MARKERHEIGHT].IsExplicitlySet() || + mLengthAttributes[MARKERHEIGHT].GetAnimValInSpecifiedUnits() > 0); +} + +SVGElement::LengthAttributesInfo SVGMarkerElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +SVGElement::EnumAttributesInfo SVGMarkerElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGAnimatedOrient* SVGMarkerElement::GetAnimatedOrient() { return &mOrient; } + +SVGAnimatedViewBox* SVGMarkerElement::GetAnimatedViewBox() { return &mViewBox; } + +SVGAnimatedPreserveAspectRatio* +SVGMarkerElement::GetAnimatedPreserveAspectRatio() { + return &mPreserveAspectRatio; +} + +//---------------------------------------------------------------------- +// public helpers + +gfx::Matrix SVGMarkerElement::GetMarkerTransform(float aStrokeWidth, + const SVGMark& aMark) { + float scale = + mEnumAttributes[MARKERUNITS].GetAnimValue() == SVG_MARKERUNITS_STROKEWIDTH + ? aStrokeWidth + : 1.0f; + + float angle; + switch (mOrient.GetAnimType()) { + case SVG_MARKER_ORIENT_AUTO: + angle = aMark.angle; + break; + case SVG_MARKER_ORIENT_AUTO_START_REVERSE: + angle = aMark.angle + (aMark.type == SVGMark::eStart ? M_PI : 0.0f); + break; + default: // SVG_MARKER_ORIENT_ANGLE + angle = mOrient.GetAnimValue() * M_PI / 180.0f; + break; + } + + return gfx::Matrix(cos(angle) * scale, sin(angle) * scale, + -sin(angle) * scale, cos(angle) * scale, aMark.x, aMark.y); +} + +SVGViewBox SVGMarkerElement::GetViewBox() { + if (mViewBox.HasRect()) { + return mViewBox.GetAnimValue(); + } + return SVGViewBox(0, 0, + mLengthAttributes[MARKERWIDTH].GetAnimValue(mCoordCtx), + mLengthAttributes[MARKERHEIGHT].GetAnimValue(mCoordCtx)); +} + +gfx::Matrix SVGMarkerElement::GetViewBoxTransform() { + if (!mViewBoxToViewportTransform) { + float viewportWidth = + mLengthAttributes[MARKERWIDTH].GetAnimValue(mCoordCtx); + float viewportHeight = + mLengthAttributes[MARKERHEIGHT].GetAnimValue(mCoordCtx); + + SVGViewBox viewbox = GetViewBox(); + + MOZ_ASSERT(viewbox.width > 0.0f && viewbox.height > 0.0f, + "Rendering should be disabled"); + + gfx::Matrix viewBoxTM = SVGContentUtils::GetViewBoxTransform( + viewportWidth, viewportHeight, viewbox.x, viewbox.y, viewbox.width, + viewbox.height, mPreserveAspectRatio); + + float refX = mLengthAttributes[REFX].GetAnimValue(mCoordCtx); + float refY = mLengthAttributes[REFY].GetAnimValue(mCoordCtx); + + gfx::Point ref = viewBoxTM.TransformPoint(gfx::Point(refX, refY)); + + Matrix TM = viewBoxTM; + TM.PostTranslate(-ref.x, -ref.y); + + mViewBoxToViewportTransform = MakeUnique(TM); + } + + return *mViewBoxToViewportTransform; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGMarkerElement.h b/dom/svg/SVGMarkerElement.h new file mode 100644 index 0000000000..3904b36a87 --- /dev/null +++ b/dom/svg/SVGMarkerElement.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGMARKERELEMENT_H_ +#define DOM_SVG_SVGMARKERELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedOrient.h" +#include "SVGAnimatedPreserveAspectRatio.h" +#include "SVGAnimatedViewBox.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGMarkerElementBinding.h" +#include "mozilla/UniquePtr.h" + +nsresult NS_NewSVGMarkerElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla { + +struct SVGMark; +class SVGMarkerFrame; + +namespace dom { + +class DOMSVGAnimatedAngle; +class DOMSVGAnimatedEnumeration; + +using SVGMarkerElementBase = SVGElement; + +class SVGMarkerElement final : public SVGMarkerElementBase { + friend class mozilla::SVGMarkerFrame; + + protected: + friend nsresult(::NS_NewSVGMarkerElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGMarkerElement( + already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + + public: + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + // public helpers + gfx::Matrix GetMarkerTransform(float aStrokeWidth, const SVGMark& aMark); + SVGViewBox GetViewBox(); + gfx::Matrix GetViewBoxTransform(); + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed ViewBox(); + already_AddRefed PreserveAspectRatio(); + already_AddRefed RefX(); + already_AddRefed RefY(); + already_AddRefed MarkerUnits(); + already_AddRefed MarkerWidth(); + already_AddRefed MarkerHeight(); + already_AddRefed OrientType(); + already_AddRefed OrientAngle(); + void SetOrientToAuto(); + void SetOrientToAngle(DOMSVGAngle& aAngle); + + protected: + void SetParentCoordCtxProvider(SVGViewportElement* aContext); + + LengthAttributesInfo GetLengthInfo() override; + EnumAttributesInfo GetEnumInfo() override; + SVGAnimatedOrient* GetAnimatedOrient() override; + virtual SVGAnimatedPreserveAspectRatio* GetAnimatedPreserveAspectRatio() + override; + SVGAnimatedViewBox* GetAnimatedViewBox() override; + + enum { REFX, REFY, MARKERWIDTH, MARKERHEIGHT }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; + + enum { MARKERUNITS }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sUnitsMap[]; + static EnumInfo sEnumInfo[1]; + + SVGAnimatedOrient mOrient; + SVGAnimatedViewBox mViewBox; + SVGAnimatedPreserveAspectRatio mPreserveAspectRatio; + + SVGViewportElement* mCoordCtx; + UniquePtr mViewBoxToViewportTransform; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGMARKERELEMENT_H_ diff --git a/dom/svg/SVGMaskElement.cpp b/dom/svg/SVGMaskElement.cpp new file mode 100644 index 0000000000..24aaa72382 --- /dev/null +++ b/dom/svg/SVGMaskElement.cpp @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGMaskElement.h" + +#include "nsGkAtoms.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGMaskElementBinding.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Mask) + +namespace mozilla::dom { + +using namespace SVGUnitTypes_Binding; + +JSObject* SVGMaskElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGMaskElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//--------------------- Masks ------------------------ + +SVGElement::LengthInfo SVGMaskElement::sLengthInfo[4] = { + {nsGkAtoms::x, -10, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::y, -10, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, + {nsGkAtoms::width, 120, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::height, 120, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, +}; + +SVGElement::EnumInfo SVGMaskElement::sEnumInfo[2] = { + {nsGkAtoms::maskUnits, sSVGUnitTypesMap, SVG_UNIT_TYPE_OBJECTBOUNDINGBOX}, + {nsGkAtoms::maskContentUnits, sSVGUnitTypesMap, + SVG_UNIT_TYPE_USERSPACEONUSE}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGMaskElement::SVGMaskElement( + already_AddRefed&& aNodeInfo) + : SVGMaskElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode method + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGMaskElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGMaskElement::MaskUnits() { + return mEnumAttributes[MASKUNITS].ToDOMAnimatedEnum(this); +} + +already_AddRefed SVGMaskElement::MaskContentUnits() { + return mEnumAttributes[MASKCONTENTUNITS].ToDOMAnimatedEnum(this); +} + +already_AddRefed SVGMaskElement::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGMaskElement::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGMaskElement::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGMaskElement::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +bool SVGMaskElement::HasValidDimensions() const { + return (!mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() || + mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0) && + (!mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() || + mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0); +} + +SVGElement::LengthAttributesInfo SVGMaskElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +SVGElement::EnumAttributesInfo SVGMaskElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGMaskElement.h b/dom/svg/SVGMaskElement.h new file mode 100644 index 0000000000..229494bb43 --- /dev/null +++ b/dom/svg/SVGMaskElement.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGMASKELEMENT_H_ +#define DOM_SVG_SVGMASKELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedLength.h" +#include "mozilla/dom/SVGElement.h" + +nsresult NS_NewSVGMaskElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla { +class SVGMaskFrame; + +namespace dom { + +//--------------------- Masks ------------------------ + +using SVGMaskElementBase = SVGElement; + +class SVGMaskElement final : public SVGMaskElementBase { + friend class mozilla::SVGMaskFrame; + + protected: + friend nsresult(::NS_NewSVGMaskElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGMaskElement(already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + + public: + // nsIContent interface + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + // WebIDL + already_AddRefed MaskUnits(); + already_AddRefed MaskContentUnits(); + already_AddRefed X(); + already_AddRefed Y(); + already_AddRefed Width(); + already_AddRefed Height(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + EnumAttributesInfo GetEnumInfo() override; + + enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; + + enum { MASKUNITS, MASKCONTENTUNITS }; + SVGAnimatedEnumeration mEnumAttributes[2]; + static EnumInfo sEnumInfo[2]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGMASKELEMENT_H_ diff --git a/dom/svg/SVGMatrix.cpp b/dom/svg/SVGMatrix.cpp new file mode 100644 index 0000000000..e9df95c7be --- /dev/null +++ b/dom/svg/SVGMatrix.cpp @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGMatrix.h" +#include "nsError.h" +#include +#include "mozilla/dom/SVGMatrixBinding.h" +#include "mozilla/FloatingPoint.h" + +const double radPerDegree = 2.0 * M_PI / 360.0; + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(SVGMatrix, mTransform) + +DOMSVGTransform* SVGMatrix::GetParentObject() const { return mTransform; } + +JSObject* SVGMatrix::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGMatrix_Binding::Wrap(aCx, this, aGivenProto); +} + +void SVGMatrix::SetA(float aA, ErrorResult& rv) { + if (IsAnimVal()) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + gfxMatrix mx = GetMatrix(); + mx._11 = aA; + SetMatrix(mx); +} + +void SVGMatrix::SetB(float aB, ErrorResult& rv) { + if (IsAnimVal()) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + gfxMatrix mx = GetMatrix(); + mx._12 = aB; + SetMatrix(mx); +} + +void SVGMatrix::SetC(float aC, ErrorResult& rv) { + if (IsAnimVal()) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + gfxMatrix mx = GetMatrix(); + mx._21 = aC; + SetMatrix(mx); +} + +void SVGMatrix::SetD(float aD, ErrorResult& rv) { + if (IsAnimVal()) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + gfxMatrix mx = GetMatrix(); + mx._22 = aD; + SetMatrix(mx); +} + +void SVGMatrix::SetE(float aE, ErrorResult& rv) { + if (IsAnimVal()) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + gfxMatrix mx = GetMatrix(); + mx._31 = aE; + SetMatrix(mx); +} + +void SVGMatrix::SetF(float aF, ErrorResult& rv) { + if (IsAnimVal()) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + + gfxMatrix mx = GetMatrix(); + mx._32 = aF; + SetMatrix(mx); +} + +already_AddRefed SVGMatrix::Multiply(SVGMatrix& aMatrix) { + RefPtr matrix = new SVGMatrix(aMatrix.GetMatrix() * GetMatrix()); + return matrix.forget(); +} + +already_AddRefed SVGMatrix::Inverse(ErrorResult& rv) { + gfxMatrix mat = GetMatrix(); + if (!mat.Invert()) { + rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + RefPtr matrix = new SVGMatrix(mat); + return matrix.forget(); +} + +already_AddRefed SVGMatrix::Translate(float x, float y) { + RefPtr matrix = + new SVGMatrix(gfxMatrix(GetMatrix()).PreTranslate(gfxPoint(x, y))); + return matrix.forget(); +} + +already_AddRefed SVGMatrix::Scale(float scaleFactor) { + return ScaleNonUniform(scaleFactor, scaleFactor); +} + +already_AddRefed SVGMatrix::ScaleNonUniform(float scaleFactorX, + float scaleFactorY) { + RefPtr matrix = new SVGMatrix( + gfxMatrix(GetMatrix()).PreScale(scaleFactorX, scaleFactorY)); + return matrix.forget(); +} + +already_AddRefed SVGMatrix::Rotate(float angle) { + RefPtr matrix = + new SVGMatrix(gfxMatrix(GetMatrix()).PreRotate(angle * radPerDegree)); + return matrix.forget(); +} + +already_AddRefed SVGMatrix::RotateFromVector(float x, float y, + ErrorResult& rv) { + if (x == 0.0 || y == 0.0) { + rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); + return nullptr; + } + + RefPtr matrix = + new SVGMatrix(gfxMatrix(GetMatrix()).PreRotate(atan2(y, x))); + return matrix.forget(); +} + +already_AddRefed SVGMatrix::FlipX() { + const gfxMatrix& mx = GetMatrix(); + RefPtr matrix = new SVGMatrix( + gfxMatrix(-mx._11, -mx._12, mx._21, mx._22, mx._31, mx._32)); + return matrix.forget(); +} + +already_AddRefed SVGMatrix::FlipY() { + const gfxMatrix& mx = GetMatrix(); + RefPtr matrix = new SVGMatrix( + gfxMatrix(mx._11, mx._12, -mx._21, -mx._22, mx._31, mx._32)); + return matrix.forget(); +} + +already_AddRefed SVGMatrix::SkewX(float angle, ErrorResult& rv) { + double ta = tan(angle * radPerDegree); + if (!std::isfinite(ta)) { + rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); + return nullptr; + } + + const gfxMatrix& mx = GetMatrix(); + gfxMatrix skewMx(mx._11, mx._12, mx._21 + mx._11 * ta, mx._22 + mx._12 * ta, + mx._31, mx._32); + RefPtr matrix = new SVGMatrix(skewMx); + return matrix.forget(); +} + +already_AddRefed SVGMatrix::SkewY(float angle, ErrorResult& rv) { + double ta = tan(angle * radPerDegree); + if (!std::isfinite(ta)) { + rv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); + return nullptr; + } + + const gfxMatrix& mx = GetMatrix(); + gfxMatrix skewMx(mx._11 + mx._21 * ta, mx._12 + mx._22 * ta, mx._21, mx._22, + mx._31, mx._32); + + RefPtr matrix = new SVGMatrix(skewMx); + return matrix.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGMatrix.h b/dom/svg/SVGMatrix.h new file mode 100644 index 0000000000..2a5018c988 --- /dev/null +++ b/dom/svg/SVGMatrix.h @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Notes on transforms in Mozilla and the SVG code. + * + * It's important to note that the matrix convention used in the SVG standard + * is the opposite convention to the one used in the Mozilla code or, more + * specifically, the convention used in Thebes code (code using gfxMatrix). + * Whereas the SVG standard uses the column vector convention, Thebes code uses + * the row vector convention. Thus, whereas in the SVG standard you have + * [M1][M2][M3]|p|, in Thebes you have |p|'[M3]'[M2]'[M1]'. In other words, the + * following are equivalent: + * + * / a1 c1 tx1 \ / a2 c2 tx2 \ / a3 c3 tx3 \ / x \ + * SVG: | b1 d1 ty1 | | b2 d2 ty2 | | b3 d3 ty3 | | y | + * \ 0 0 1 / \ 0 0 1 / \ 0 0 1 / \ 1 / + * + * / a3 b3 0 \ / a2 b2 0 \ / a1 b1 0 \ + * Thebes: [ x y 1 ] | c3 d3 0 | | c2 d2 0 | | c1 d1 0 | + * \ tx3 ty3 1 / \ tx2 ty2 1 / \ tx1 ty1 1 / + * + * Because the Thebes representation of a transform is the transpose of the SVG + * representation, our transform order must be reversed when representing SVG + * transforms using gfxMatrix in the SVG code. Since the SVG implementation + * stores and obtains matrices in SVG order, to do this we must pre-multiply + * gfxMatrix objects that represent SVG transforms instead of post-multiplying + * them as we would for matrices using SVG's column vector convention. + * Pre-multiplying may look wrong if you're only familiar with the SVG + * convention, but in that case hopefully the above explanation clears things + * up. + */ + +#ifndef DOM_SVG_SVGMATRIX_H_ +#define DOM_SVG_SVGMATRIX_H_ + +#include "DOMSVGTransform.h" +#include "gfxMatrix.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "mozilla/Attributes.h" + +namespace mozilla::dom { + +/** + * DOM wrapper for an SVG matrix. + */ +class SVGMatrix final : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(SVGMatrix) + NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(SVGMatrix) + + /** + * Ctor for SVGMatrix objects that belong to a DOMSVGTransform. + */ + explicit SVGMatrix(DOMSVGTransform& aTransform) : mTransform(&aTransform) {} + + /** + * Ctors for SVGMatrix objects created independently of a DOMSVGTransform. + */ + // Default ctor for gfxMatrix will produce identity mx + SVGMatrix() = default; + + explicit SVGMatrix(const gfxMatrix& aMatrix) : mMatrix(aMatrix) {} + + // WebIDL + DOMSVGTransform* GetParentObject() const; + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + float A() const { return static_cast(GetMatrix()._11); } + void SetA(float aA, ErrorResult& rv); + float B() const { return static_cast(GetMatrix()._12); } + void SetB(float aB, ErrorResult& rv); + float C() const { return static_cast(GetMatrix()._21); } + void SetC(float aC, ErrorResult& rv); + float D() const { return static_cast(GetMatrix()._22); } + void SetD(float aD, ErrorResult& rv); + float E() const { return static_cast(GetMatrix()._31); } + void SetE(float aE, ErrorResult& rv); + float F() const { return static_cast(GetMatrix()._32); } + void SetF(float aF, ErrorResult& rv); + already_AddRefed Multiply(SVGMatrix& aMatrix); + already_AddRefed Inverse(ErrorResult& aRv); + already_AddRefed Translate(float x, float y); + already_AddRefed Scale(float scaleFactor); + already_AddRefed ScaleNonUniform(float scaleFactorX, + float scaleFactorY); + already_AddRefed Rotate(float angle); + already_AddRefed RotateFromVector(float x, float y, + ErrorResult& aRv); + already_AddRefed FlipX(); + already_AddRefed FlipY(); + already_AddRefed SkewX(float angle, ErrorResult& rv); + already_AddRefed SkewY(float angle, ErrorResult& rv); + + private: + ~SVGMatrix() = default; + + const gfxMatrix& GetMatrix() const { + return mTransform ? mTransform->Matrixgfx() : mMatrix; + } + + void SetMatrix(const gfxMatrix& aMatrix) { + if (mTransform) { + mTransform->SetMatrix(aMatrix); + } else { + mMatrix = aMatrix; + } + } + + bool IsAnimVal() const { + return mTransform ? mTransform->IsAnimVal() : false; + } + + RefPtr mTransform; + + // Typically we operate on the matrix data accessed via mTransform but for + // matrices that exist independently of an DOMSVGTransform we use mMatrix + // below. + gfxMatrix mMatrix; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGMATRIX_H_ diff --git a/dom/svg/SVGMetadataElement.cpp b/dom/svg/SVGMetadataElement.cpp new file mode 100644 index 0000000000..e2774f264a --- /dev/null +++ b/dom/svg/SVGMetadataElement.cpp @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGMetadataElement.h" +#include "mozilla/dom/SVGMetadataElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Metadata) + +namespace mozilla::dom { + +JSObject* SVGMetadataElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGMetadataElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGMetadataElement::SVGMetadataElement( + already_AddRefed&& aNodeInfo) + : SVGMetadataElementBase(std::move(aNodeInfo)) {} + +nsresult SVGMetadataElement::Init() { return NS_OK; } + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGMetadataElement) + +} // namespace mozilla::dom diff --git a/dom/svg/SVGMetadataElement.h b/dom/svg/SVGMetadataElement.h new file mode 100644 index 0000000000..3c1669ac6a --- /dev/null +++ b/dom/svg/SVGMetadataElement.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGMETADATAELEMENT_H_ +#define DOM_SVG_SVGMETADATAELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "SVGElement.h" + +nsresult NS_NewSVGMetadataElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGMetadataElementBase = SVGElement; + +class SVGMetadataElement final : public SVGMetadataElementBase { + protected: + friend nsresult(::NS_NewSVGMetadataElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGMetadataElement( + already_AddRefed&& aNodeInfo); + + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + nsresult Init(); + + public: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGMETADATAELEMENT_H_ diff --git a/dom/svg/SVGMotionSMILAnimationFunction.cpp b/dom/svg/SVGMotionSMILAnimationFunction.cpp new file mode 100644 index 0000000000..d950617f0f --- /dev/null +++ b/dom/svg/SVGMotionSMILAnimationFunction.cpp @@ -0,0 +1,417 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGMotionSMILAnimationFunction.h" + +#include "mozilla/dom/SVGAnimationElement.h" +#include "mozilla/dom/SVGPathElement.h" +#include "mozilla/dom/SVGMPathElement.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/SMILParserUtils.h" +#include "nsAttrValue.h" +#include "nsAttrValueInlines.h" +#include "SVGAnimatedOrient.h" +#include "SVGMotionSMILPathUtils.h" +#include "SVGMotionSMILType.h" +#include "SVGPathDataParser.h" + +using namespace mozilla::dom; +using namespace mozilla::dom::SVGAngle_Binding; +using namespace mozilla::gfx; + +namespace mozilla { + +SVGMotionSMILAnimationFunction::SVGMotionSMILAnimationFunction() + : mRotateType(eRotateType_Explicit), + mRotateAngle(0.0f), + mPathSourceType(ePathSourceType_None), + mIsPathStale(true) // Try to initialize path on first GetValues call +{} + +void SVGMotionSMILAnimationFunction::MarkStaleIfAttributeAffectsPath( + nsAtom* aAttribute) { + bool isAffected; + if (aAttribute == nsGkAtoms::path) { + isAffected = (mPathSourceType <= ePathSourceType_PathAttr); + } else if (aAttribute == nsGkAtoms::values) { + isAffected = (mPathSourceType <= ePathSourceType_ValuesAttr); + } else if (aAttribute == nsGkAtoms::from || aAttribute == nsGkAtoms::to) { + isAffected = (mPathSourceType <= ePathSourceType_ToAttr); + } else if (aAttribute == nsGkAtoms::by) { + isAffected = (mPathSourceType <= ePathSourceType_ByAttr); + } else { + MOZ_ASSERT_UNREACHABLE( + "Should only call this method for path-describing " + "attrs"); + isAffected = false; + } + + if (isAffected) { + mIsPathStale = true; + mHasChanged = true; + } +} + +bool SVGMotionSMILAnimationFunction::SetAttr(nsAtom* aAttribute, + const nsAString& aValue, + nsAttrValue& aResult, + nsresult* aParseResult) { + // Handle motion-specific attrs + if (aAttribute == nsGkAtoms::keyPoints) { + nsresult rv = SetKeyPoints(aValue, aResult); + if (aParseResult) { + *aParseResult = rv; + } + } else if (aAttribute == nsGkAtoms::rotate) { + nsresult rv = SetRotate(aValue, aResult); + if (aParseResult) { + *aParseResult = rv; + } + } else if (aAttribute == nsGkAtoms::path || aAttribute == nsGkAtoms::by || + aAttribute == nsGkAtoms::from || aAttribute == nsGkAtoms::to || + aAttribute == nsGkAtoms::values) { + aResult.SetTo(aValue); + MarkStaleIfAttributeAffectsPath(aAttribute); + if (aParseResult) { + *aParseResult = NS_OK; + } + } else { + // Defer to superclass method + return SMILAnimationFunction::SetAttr(aAttribute, aValue, aResult, + aParseResult); + } + + return true; +} + +bool SVGMotionSMILAnimationFunction::UnsetAttr(nsAtom* aAttribute) { + if (aAttribute == nsGkAtoms::keyPoints) { + UnsetKeyPoints(); + } else if (aAttribute == nsGkAtoms::rotate) { + UnsetRotate(); + } else if (aAttribute == nsGkAtoms::path || aAttribute == nsGkAtoms::by || + aAttribute == nsGkAtoms::from || aAttribute == nsGkAtoms::to || + aAttribute == nsGkAtoms::values) { + MarkStaleIfAttributeAffectsPath(aAttribute); + } else { + // Defer to superclass method + return SMILAnimationFunction::UnsetAttr(aAttribute); + } + + return true; +} + +SMILAnimationFunction::SMILCalcMode +SVGMotionSMILAnimationFunction::GetCalcMode() const { + const nsAttrValue* value = GetAttr(nsGkAtoms::calcMode); + if (!value) { + return CALC_PACED; // animateMotion defaults to calcMode="paced" + } + + return SMILCalcMode(value->GetEnumValue()); +} + +//---------------------------------------------------------------------- +// Helpers for GetValues + +/* + * Returns the first child of the given element + */ + +static SVGMPathElement* GetFirstMPathChild(nsIContent* aElem) { + for (nsIContent* child = aElem->GetFirstChild(); child; + child = child->GetNextSibling()) { + if (child->IsSVGElement(nsGkAtoms::mpath)) { + return static_cast(child); + } + } + + return nullptr; +} + +void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromBasicAttrs( + const nsIContent* aContextElem) { + MOZ_ASSERT(!HasAttr(nsGkAtoms::path), + "Should be using |path| attr if we have it"); + MOZ_ASSERT(!mPath, "regenerating when we already have path"); + MOZ_ASSERT(mPathVertices.IsEmpty(), + "regenerating when we already have vertices"); + + const auto* context = SVGElement::FromNode(aContextElem); + if (!context) { + NS_ERROR("Uh oh, SVG animateMotion element targeting a non-SVG node"); + return; + } + SVGMotionSMILPathUtils::PathGenerator pathGenerator(context); + + bool success = false; + if (HasAttr(nsGkAtoms::values)) { + // Generate path based on our values array + mPathSourceType = ePathSourceType_ValuesAttr; + const nsAString& valuesStr = GetAttr(nsGkAtoms::values)->GetStringValue(); + SVGMotionSMILPathUtils::MotionValueParser parser(&pathGenerator, + &mPathVertices); + success = SMILParserUtils::ParseValuesGeneric(valuesStr, parser); + } else if (HasAttr(nsGkAtoms::to) || HasAttr(nsGkAtoms::by)) { + // Apply 'from' value (or a dummy 0,0 'from' value) + if (HasAttr(nsGkAtoms::from)) { + const nsAString& fromStr = GetAttr(nsGkAtoms::from)->GetStringValue(); + success = pathGenerator.MoveToAbsolute(fromStr); + if (!mPathVertices.AppendElement(0.0, fallible)) { + success = false; + } + } else { + // Create dummy 'from' value at 0,0, if we're doing by-animation. + // (NOTE: We don't add the dummy 0-point to our list for *to-animation*, + // because the SMILAnimationFunction logic for to-animation doesn't + // expect a dummy value. It only expects one value: the final 'to' value.) + pathGenerator.MoveToOrigin(); + success = true; + if (!HasAttr(nsGkAtoms::to)) { + if (!mPathVertices.AppendElement(0.0, fallible)) { + success = false; + } + } + } + + // Apply 'to' or 'by' value + if (success) { + double dist; + if (HasAttr(nsGkAtoms::to)) { + mPathSourceType = ePathSourceType_ToAttr; + const nsAString& toStr = GetAttr(nsGkAtoms::to)->GetStringValue(); + success = pathGenerator.LineToAbsolute(toStr, dist); + } else { // HasAttr(nsGkAtoms::by) + mPathSourceType = ePathSourceType_ByAttr; + const nsAString& byStr = GetAttr(nsGkAtoms::by)->GetStringValue(); + success = pathGenerator.LineToRelative(byStr, dist); + } + if (success) { + if (!mPathVertices.AppendElement(dist, fallible)) { + success = false; + } + } + } + } + if (success) { + mPath = pathGenerator.GetResultingPath(); + } else { + // Parse failure. Leave path as null, and clear path-related member data. + mPathVertices.Clear(); + } +} + +void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromMpathElem( + SVGMPathElement* aMpathElem) { + mPathSourceType = ePathSourceType_Mpath; + + // Use the shape that's the target of our chosen child. + SVGGeometryElement* shapeElem = aMpathElem->GetReferencedPath(); + if (shapeElem && shapeElem->HasValidDimensions()) { + bool ok = shapeElem->GetDistancesFromOriginToEndsOfVisibleSegments( + &mPathVertices); + if (ok && mPathVertices.Length()) { + mPath = shapeElem->GetOrBuildPathForMeasuring(); + } + } +} + +void SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromPathAttr() { + const nsAString& pathSpec = GetAttr(nsGkAtoms::path)->GetStringValue(); + mPathSourceType = ePathSourceType_PathAttr; + + // Generate Path from |path| attr + SVGPathData path; + SVGPathDataParser pathParser(pathSpec, &path); + + // We ignore any failure returned from Parse() since the SVG spec says to + // accept all segments up to the first invalid token. Instead we must + // explicitly check that the parse produces at least one path segment (if + // the path data doesn't begin with a valid "M", then it's invalid). + pathParser.Parse(); + if (!path.Length()) { + return; + } + + mPath = path.BuildPathForMeasuring(); + bool ok = path.GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices); + if (!ok || !mPathVertices.Length()) { + mPath = nullptr; + mPathVertices.Clear(); + } +} + +// Helper to regenerate our path representation & its list of vertices +void SVGMotionSMILAnimationFunction::RebuildPathAndVertices( + const nsIContent* aTargetElement) { + MOZ_ASSERT(mIsPathStale, "rebuilding path when it isn't stale"); + + // Clear stale data + mPath = nullptr; + mPathVertices.Clear(); + mPathSourceType = ePathSourceType_None; + + // Do we have a mpath child? if so, it trumps everything. Otherwise, we look + // through our list of path-defining attributes, in order of priority. + SVGMPathElement* firstMpathChild = GetFirstMPathChild(mAnimationElement); + + if (firstMpathChild) { + RebuildPathAndVerticesFromMpathElem(firstMpathChild); + mValueNeedsReparsingEverySample = false; + } else if (HasAttr(nsGkAtoms::path)) { + RebuildPathAndVerticesFromPathAttr(); + mValueNeedsReparsingEverySample = false; + } else { + // Get path & vertices from basic SMIL attrs: from/by/to/values + + RebuildPathAndVerticesFromBasicAttrs(aTargetElement); + mValueNeedsReparsingEverySample = true; + } + mIsPathStale = false; +} + +bool SVGMotionSMILAnimationFunction::GenerateValuesForPathAndPoints( + Path* aPath, bool aIsKeyPoints, FallibleTArray& aPointDistances, + SMILValueArray& aResult) { + MOZ_ASSERT(aResult.IsEmpty(), "outparam is non-empty"); + + // If we're using "keyPoints" as our list of input distances, then we need + // to de-normalize from the [0, 1] scale to the [0, totalPathLen] scale. + double distanceMultiplier = aIsKeyPoints ? aPath->ComputeLength() : 1.0; + const uint32_t numPoints = aPointDistances.Length(); + for (uint32_t i = 0; i < numPoints; ++i) { + double curDist = aPointDistances[i] * distanceMultiplier; + if (!aResult.AppendElement(SVGMotionSMILType::ConstructSMILValue( + aPath, curDist, mRotateType, mRotateAngle), + fallible)) { + return false; + } + } + return true; +} + +nsresult SVGMotionSMILAnimationFunction::GetValues(const SMILAttr& aSMILAttr, + SMILValueArray& aResult) { + if (mIsPathStale) { + RebuildPathAndVertices(aSMILAttr.GetTargetNode()); + } + MOZ_ASSERT(!mIsPathStale, "Forgot to clear 'is path stale' state"); + + if (!mPath) { + // This could be due to e.g. a parse error. + MOZ_ASSERT(mPathVertices.IsEmpty(), "have vertices but no path"); + return NS_ERROR_FAILURE; + } + MOZ_ASSERT(!mPathVertices.IsEmpty(), "have a path but no vertices"); + + // Now: Make the actual list of SMILValues (using keyPoints, if set) + bool isUsingKeyPoints = !mKeyPoints.IsEmpty(); + bool success = GenerateValuesForPathAndPoints( + mPath, isUsingKeyPoints, isUsingKeyPoints ? mKeyPoints : mPathVertices, + aResult); + if (!success) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +void SVGMotionSMILAnimationFunction::CheckValueListDependentAttrs( + uint32_t aNumValues) { + // Call superclass method. + SMILAnimationFunction::CheckValueListDependentAttrs(aNumValues); + + // Added behavior: Do checks specific to keyPoints. + CheckKeyPoints(); +} + +bool SVGMotionSMILAnimationFunction::IsToAnimation() const { + // Rely on inherited method, but not if we have an child or a |path| + // attribute, because they'll override any 'to' attr we might have. + // NOTE: We can't rely on mPathSourceType, because it might not have been + // set to a useful value yet (or it might be stale). + return !GetFirstMPathChild(mAnimationElement) && !HasAttr(nsGkAtoms::path) && + SMILAnimationFunction::IsToAnimation(); +} + +void SVGMotionSMILAnimationFunction::CheckKeyPoints() { + if (!HasAttr(nsGkAtoms::keyPoints)) return; + + // attribute is ignored for calcMode="paced" (even if it's got errors) + if (GetCalcMode() == CALC_PACED) { + SetKeyPointsErrorFlag(false); + } + + if (mKeyPoints.Length() != mKeyTimes.Length()) { + // there must be exactly as many keyPoints as keyTimes + SetKeyPointsErrorFlag(true); + return; + } + + // Nothing else to check -- we can catch all keyPoints errors elsewhere. + // - Formatting & range issues will be caught in SetKeyPoints, and will + // result in an empty mKeyPoints array, which will drop us into the error + // case above. +} + +nsresult SVGMotionSMILAnimationFunction::SetKeyPoints( + const nsAString& aKeyPoints, nsAttrValue& aResult) { + mKeyPoints.Clear(); + aResult.SetTo(aKeyPoints); + + mHasChanged = true; + + if (!SMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyPoints, false, + mKeyPoints)) { + mKeyPoints.Clear(); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void SVGMotionSMILAnimationFunction::UnsetKeyPoints() { + mKeyPoints.Clear(); + SetKeyPointsErrorFlag(false); + mHasChanged = true; +} + +nsresult SVGMotionSMILAnimationFunction::SetRotate(const nsAString& aRotate, + nsAttrValue& aResult) { + mHasChanged = true; + + aResult.SetTo(aRotate); + if (aRotate.EqualsLiteral("auto")) { + mRotateType = eRotateType_Auto; + } else if (aRotate.EqualsLiteral("auto-reverse")) { + mRotateType = eRotateType_AutoReverse; + } else { + mRotateType = eRotateType_Explicit; + + uint16_t angleUnit; + if (!SVGAnimatedOrient::GetValueFromString(aRotate, mRotateAngle, + &angleUnit)) { + mRotateAngle = 0.0f; // set default rotate angle + // XXX report to console? + return NS_ERROR_DOM_SYNTAX_ERR; + } + + // Convert to radian units, if we're not already in radians. + if (angleUnit != SVG_ANGLETYPE_RAD) { + mRotateAngle *= SVGAnimatedOrient::GetDegreesPerUnit(angleUnit) / + SVGAnimatedOrient::GetDegreesPerUnit(SVG_ANGLETYPE_RAD); + } + } + return NS_OK; +} + +void SVGMotionSMILAnimationFunction::UnsetRotate() { + mRotateAngle = 0.0f; // default value + mRotateType = eRotateType_Explicit; + mHasChanged = true; +} + +} // namespace mozilla diff --git a/dom/svg/SVGMotionSMILAnimationFunction.h b/dom/svg/SVGMotionSMILAnimationFunction.h new file mode 100644 index 0000000000..fd49092d02 --- /dev/null +++ b/dom/svg/SVGMotionSMILAnimationFunction.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGMOTIONSMILANIMATIONFUNCTION_H_ +#define DOM_SVG_SVGMOTIONSMILANIMATIONFUNCTION_H_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SMILAnimationFunction.h" +#include "SVGMotionSMILType.h" +#include "nsTArray.h" + +class nsAttrValue; +class nsAtom; +class nsIContent; + +namespace mozilla { + +class SMILAttr; +class SMILValue; + +namespace dom { +class SVGMPathElement; +} // namespace dom + +//---------------------------------------------------------------------- +// SVGMotionSMILAnimationFunction +// +// Subclass of SMILAnimationFunction to support a few extra features offered +// by the element. +// +class SVGMotionSMILAnimationFunction final : public SMILAnimationFunction { + using Path = mozilla::gfx::Path; + + public: + SVGMotionSMILAnimationFunction(); + bool SetAttr(nsAtom* aAttribute, const nsAString& aValue, + nsAttrValue& aResult, nsresult* aParseResult = nullptr) override; + bool UnsetAttr(nsAtom* aAttribute) override; + + // Method to allow our owner-element to signal us when our + // has changed or been added/removed. When that happens, we need to + // mark ourselves as changed so we'll get recomposed, and mark our path data + // as stale so it'll get regenerated (regardless of mPathSourceType, since + // trumps all the other sources of path data) + void MpathChanged() { mIsPathStale = mHasChanged = true; } + + protected: + enum PathSourceType { + // NOTE: Ordering matters here. Higher-priority path-descriptors should + // have higher enumerated values + ePathSourceType_None, // uninitialized or not applicable + ePathSourceType_ByAttr, // by or from-by animation + ePathSourceType_ToAttr, // to or from-to animation + ePathSourceType_ValuesAttr, + ePathSourceType_PathAttr, + ePathSourceType_Mpath + }; + + SMILCalcMode GetCalcMode() const override; + virtual nsresult GetValues(const SMILAttr& aSMILAttr, + SMILValueArray& aResult) override; + void CheckValueListDependentAttrs(uint32_t aNumValues) override; + + bool IsToAnimation() const override; + + void CheckKeyPoints(); + nsresult SetKeyPoints(const nsAString& aKeyPoints, nsAttrValue& aResult); + void UnsetKeyPoints(); + nsresult SetRotate(const nsAString& aRotate, nsAttrValue& aResult); + void UnsetRotate(); + + // Helpers for GetValues + void MarkStaleIfAttributeAffectsPath(nsAtom* aAttribute); + void RebuildPathAndVertices(const nsIContent* aTargetElement); + void RebuildPathAndVerticesFromMpathElem(dom::SVGMPathElement* aMpathElem); + void RebuildPathAndVerticesFromPathAttr(); + void RebuildPathAndVerticesFromBasicAttrs(const nsIContent* aContextElem); + bool GenerateValuesForPathAndPoints(Path* aPath, bool aIsKeyPoints, + FallibleTArray& aPointDistances, + SMILValueArray& aResult); + + // Members + // ------- + FallibleTArray mKeyPoints; // parsed from "keyPoints" attribute. + + RotateType mRotateType; // auto, auto-reverse, or explicit. + float mRotateAngle; // the angle value, if explicit. + + PathSourceType mPathSourceType; // source of our Path. + RefPtr mPath; // representation of motion path. + FallibleTArray mPathVertices; // distances of vertices along path. + + bool mIsPathStale; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGMOTIONSMILANIMATIONFUNCTION_H_ diff --git a/dom/svg/SVGMotionSMILAttr.cpp b/dom/svg/SVGMotionSMILAttr.cpp new file mode 100644 index 0000000000..c269e876af --- /dev/null +++ b/dom/svg/SVGMotionSMILAttr.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* representation of a dummy attribute targeted by element */ + +#include "SVGMotionSMILAttr.h" + +#include "mozilla/dom/SVGAnimationElement.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/SMILValue.h" +#include "SVGMotionSMILType.h" +#include "nsDebug.h" +#include "gfx2DGlue.h" + +namespace mozilla { + +nsresult SVGMotionSMILAttr::ValueFromString( + const nsAString& aStr, const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, bool& aPreventCachingOfSandwich) const { + MOZ_ASSERT_UNREACHABLE( + "Shouldn't using SMILAttr::ValueFromString for " + "parsing animateMotion's SMIL values."); + return NS_ERROR_FAILURE; +} + +SMILValue SVGMotionSMILAttr::GetBaseValue() const { + return SMILValue(&SVGMotionSMILType::sSingleton); +} + +void SVGMotionSMILAttr::ClearAnimValue() { + mSVGElement->SetAnimateMotionTransform(nullptr); +} + +nsresult SVGMotionSMILAttr::SetAnimValue(const SMILValue& aValue) { + gfx::Matrix matrix = SVGMotionSMILType::CreateMatrix(aValue); + mSVGElement->SetAnimateMotionTransform(&matrix); + return NS_OK; +} + +const nsIContent* SVGMotionSMILAttr::GetTargetNode() const { + return mSVGElement; +} + +} // namespace mozilla diff --git a/dom/svg/SVGMotionSMILAttr.h b/dom/svg/SVGMotionSMILAttr.h new file mode 100644 index 0000000000..953aeed5b0 --- /dev/null +++ b/dom/svg/SVGMotionSMILAttr.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* representation of a dummy attribute targeted by element */ + +#ifndef DOM_SVG_SVGMOTIONSMILATTR_H_ +#define DOM_SVG_SVGMOTIONSMILATTR_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILAttr.h" + +class nsIContent; + +namespace mozilla { + +class SMILValue; + +namespace dom { +class SVGAnimationElement; +class SVGElement; +} // namespace dom + +/** + * SVGMotionSMILAttr: Implements the SMILAttr interface for SMIL animations + * from . + * + * NOTE: Even though there's technically no "motion" attribute, we behave in + * many ways as if there were, for simplicity. + */ +class SVGMotionSMILAttr : public SMILAttr { + public: + explicit SVGMotionSMILAttr(dom::SVGElement* aSVGElement) + : mSVGElement(aSVGElement) {} + + // SMILAttr methods + nsresult ValueFromString(const nsAString& aStr, + const dom::SVGAnimationElement* aSrcElement, + SMILValue& aValue, + bool& aPreventCachingOfSandwich) const override; + SMILValue GetBaseValue() const override; + nsresult SetAnimValue(const SMILValue& aValue) override; + void ClearAnimValue() override; + const nsIContent* GetTargetNode() const override; + + protected: + // Raw pointers are OK here because this SVGMotionSMILAttr is both + // created & destroyed during a SMIL sample-step, during which time the DOM + // isn't modified. + dom::SVGElement* mSVGElement; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGMOTIONSMILATTR_H_ diff --git a/dom/svg/SVGMotionSMILPathUtils.cpp b/dom/svg/SVGMotionSMILPathUtils.cpp new file mode 100644 index 0000000000..628b3622c1 --- /dev/null +++ b/dom/svg/SVGMotionSMILPathUtils.cpp @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGMotionSMILPathUtils.h" + +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" // for NS_ENSURE_FINITE2 +#include "SVGContentUtils.h" +#include "SVGLength.h" + +using namespace mozilla::gfx; + +namespace mozilla { + +//---------------------------------------------------------------------- +// PathGenerator methods + +// For the dummy 'from' value used in pure by-animation & to-animation +void SVGMotionSMILPathUtils::PathGenerator::MoveToOrigin() { + MOZ_ASSERT(!mHaveReceivedCommands, + "Not expecting requests for mid-path MoveTo commands"); + mHaveReceivedCommands = true; + mPathBuilder->MoveTo(Point(0, 0)); +} + +// For 'from' and the first entry in 'values'. +bool SVGMotionSMILPathUtils::PathGenerator::MoveToAbsolute( + const nsAString& aCoordPairStr) { + MOZ_ASSERT(!mHaveReceivedCommands, + "Not expecting requests for mid-path MoveTo commands"); + mHaveReceivedCommands = true; + + float xVal, yVal; + if (!ParseCoordinatePair(aCoordPairStr, xVal, yVal)) { + return false; + } + mPathBuilder->MoveTo(Point(xVal, yVal)); + return true; +} + +// For 'to' and every entry in 'values' except the first. +bool SVGMotionSMILPathUtils::PathGenerator::LineToAbsolute( + const nsAString& aCoordPairStr, double& aSegmentDistance) { + mHaveReceivedCommands = true; + + float xVal, yVal; + if (!ParseCoordinatePair(aCoordPairStr, xVal, yVal)) { + return false; + } + Point initialPoint = mPathBuilder->CurrentPoint(); + + mPathBuilder->LineTo(Point(xVal, yVal)); + aSegmentDistance = NS_hypot(initialPoint.x - xVal, initialPoint.y - yVal); + return true; +} + +// For 'by'. +bool SVGMotionSMILPathUtils::PathGenerator::LineToRelative( + const nsAString& aCoordPairStr, double& aSegmentDistance) { + mHaveReceivedCommands = true; + + float xVal, yVal; + if (!ParseCoordinatePair(aCoordPairStr, xVal, yVal)) { + return false; + } + mPathBuilder->LineTo(mPathBuilder->CurrentPoint() + Point(xVal, yVal)); + aSegmentDistance = NS_hypot(xVal, yVal); + return true; +} + +already_AddRefed +SVGMotionSMILPathUtils::PathGenerator::GetResultingPath() { + return mPathBuilder->Finish(); +} + +//---------------------------------------------------------------------- +// Helper / protected methods + +bool SVGMotionSMILPathUtils::PathGenerator::ParseCoordinatePair( + const nsAString& aCoordPairStr, float& aXVal, float& aYVal) { + nsCharSeparatedTokenizerTemplate + tokenizer(aCoordPairStr, ','); + + SVGLength x, y; + + if (!tokenizer.hasMoreTokens() || + !x.SetValueFromString(tokenizer.nextToken())) { + return false; + } + + if (!tokenizer.hasMoreTokens() || + !y.SetValueFromString(tokenizer.nextToken())) { + return false; + } + + if (tokenizer.separatorAfterCurrentToken() || // Trailing comma. + tokenizer.hasMoreTokens()) { // More text remains + return false; + } + + float xRes = x.GetValueInUserUnits(mSVGElement, SVGContentUtils::X); + float yRes = y.GetValueInUserUnits(mSVGElement, SVGContentUtils::Y); + + NS_ENSURE_FINITE2(xRes, yRes, false); + + aXVal = xRes; + aYVal = yRes; + return true; +} + +//---------------------------------------------------------------------- +// MotionValueParser methods +bool SVGMotionSMILPathUtils::MotionValueParser::Parse( + const nsAString& aValueStr) { + bool success; + if (!mPathGenerator->HaveReceivedCommands()) { + // Interpret first value in "values" attribute as the path's initial MoveTo + success = mPathGenerator->MoveToAbsolute(aValueStr); + if (success) { + success = !!mPointDistances->AppendElement(0.0, fallible); + } + } else { + double dist; + success = mPathGenerator->LineToAbsolute(aValueStr, dist); + if (success) { + mDistanceSoFar += dist; + success = !!mPointDistances->AppendElement(mDistanceSoFar, fallible); + } + } + return success; +} + +} // namespace mozilla diff --git a/dom/svg/SVGMotionSMILPathUtils.h b/dom/svg/SVGMotionSMILPathUtils.h new file mode 100644 index 0000000000..02b3389303 --- /dev/null +++ b/dom/svg/SVGMotionSMILPathUtils.h @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Helper class to help with generating anonymous path elements for + elements to use. */ + +#ifndef DOM_SVG_SVGMOTIONSMILPATHUTILS_H_ +#define DOM_SVG_SVGMOTIONSMILPATHUTILS_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SMILParserUtils.h" +#include "mozilla/gfx/2D.h" +#include "gfxPlatform.h" +#include "nsDebug.h" +#include "nsStringFwd.h" +#include "nsTArray.h" + +namespace mozilla { + +namespace dom { +class SVGElement; +} + +class SVGMotionSMILPathUtils { + using DrawTarget = mozilla::gfx::DrawTarget; + using Path = mozilla::gfx::Path; + using PathBuilder = mozilla::gfx::PathBuilder; + + public: + // Class to assist in generating a Path, based on + // coordinates in the from/by/to/values attributes. + class PathGenerator { + public: + explicit PathGenerator(const dom::SVGElement* aSVGElement) + : mSVGElement(aSVGElement), mHaveReceivedCommands(false) { + RefPtr drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + NS_ASSERTION( + gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget( + drawTarget), + "Should support Moz2D content drawing"); + + mPathBuilder = drawTarget->CreatePathBuilder(); + } + + // Methods for adding various path commands to output path. + // Note: aCoordPairStr is expected to be a whitespace and/or + // comma-separated x,y coordinate-pair -- see description of + // "the specified values for from, by, to, and values" at + // http://www.w3.org/TR/SVG11/animate.html#AnimateMotionElement + void MoveToOrigin(); + bool MoveToAbsolute(const nsAString& aCoordPairStr); + bool LineToAbsolute(const nsAString& aCoordPairStr, + double& aSegmentDistance); + bool LineToRelative(const nsAString& aCoordPairStr, + double& aSegmentDistance); + + // Accessor to let clients check if we've received any commands yet. + inline bool HaveReceivedCommands() { return mHaveReceivedCommands; } + // Accessor to get the finalized path + already_AddRefed GetResultingPath(); + + protected: + // Helper methods + bool ParseCoordinatePair(const nsAString& aCoordPairStr, float& aXVal, + float& aYVal); + + // Member data + const dom::SVGElement* mSVGElement; // context for converting to user units + RefPtr mPathBuilder; + bool mHaveReceivedCommands; + }; + + // Class to assist in passing each subcomponent of a |values| attribute to + // a PathGenerator, for generating a corresponding Path. + class MOZ_STACK_CLASS MotionValueParser + : public SMILParserUtils::GenericValueParser { + public: + MotionValueParser(PathGenerator* aPathGenerator, + FallibleTArray* aPointDistances) + : mPathGenerator(aPathGenerator), + mPointDistances(aPointDistances), + mDistanceSoFar(0.0) { + MOZ_ASSERT(mPointDistances->IsEmpty(), + "expecting point distances array to start empty"); + } + + // SMILParserUtils::GenericValueParser interface + bool Parse(const nsAString& aValueStr) override; + + protected: + PathGenerator* mPathGenerator; + FallibleTArray* mPointDistances; + double mDistanceSoFar; + }; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGMOTIONSMILPATHUTILS_H_ diff --git a/dom/svg/SVGMotionSMILType.cpp b/dom/svg/SVGMotionSMILType.cpp new file mode 100644 index 0000000000..15521ee54d --- /dev/null +++ b/dom/svg/SVGMotionSMILType.cpp @@ -0,0 +1,459 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* implementation of nsISMILType for use by element */ + +#include "SVGMotionSMILType.h" + +#include "mozilla/SMILValue.h" +#include "mozilla/gfx/Point.h" +#include "gfx2DGlue.h" +#include "nsDebug.h" +#include "nsMathUtils.h" +#include "nsISupportsUtils.h" +#include "nsTArray.h" +#include + +using namespace mozilla::gfx; + +namespace mozilla { + +/*static*/ +SVGMotionSMILType SVGMotionSMILType::sSingleton; + +// Helper enum, for distinguishing between types of MotionSegment structs +enum SegmentType { eSegmentType_Translation, eSegmentType_PathPoint }; + +// Helper Structs: containers for params to define our MotionSegment +// (either simple translation or point-on-a-path) +struct TranslationParams { // Simple translation + float mX; + float mY; +}; +struct PathPointParams { // Point along a path + // Refcounted: need to AddRef/Release. This can't be an nsRefPtr because + // this struct is used inside a union so it can't have a default constructor. + Path* MOZ_OWNING_REF mPath; + float mDistToPoint; // Distance from path start to the point on the path that + // we're interested in. +}; + +/** + * Helper Struct: MotionSegment + * + * Instances of this class represent the points that we move between during + * . Each SMILValue will get a nsTArray of these (generally + * with at most 1 entry in the array, except for in SandwichAdd). (This + * matches our behavior in SVGTransformListSMILType.) + * + * NOTE: In general, MotionSegments are represented as points on a path + * (eSegmentType_PathPoint), so that we can easily interpolate and compute + * distance *along their path*. However, Add() outputs MotionSegments as + * simple translations (eSegmentType_Translation), because adding two points + * from a path (e.g. when accumulating a repeated animation) will generally + * take you to an arbitrary point *off* of the path. + */ +struct MotionSegment { + // Default constructor just locks us into being a Translation, and leaves + // other fields uninitialized (since client is presumably about to set them) + MotionSegment() + : mRotateType(eRotateType_Auto), + mRotateAngle(0.0), + mSegmentType(eSegmentType_Translation), + mU{} {} + + // Constructor for a translation + MotionSegment(float aX, float aY, float aRotateAngle) + : mRotateType(eRotateType_Explicit), + mRotateAngle(aRotateAngle), + mSegmentType(eSegmentType_Translation) { + mU.mTranslationParams.mX = aX; + mU.mTranslationParams.mY = aY; + } + + // Constructor for a point on a path (NOTE: AddRef's) + MotionSegment(Path* aPath, float aDistToPoint, RotateType aRotateType, + float aRotateAngle) + : mRotateType(aRotateType), + mRotateAngle(aRotateAngle), + mSegmentType(eSegmentType_PathPoint) { + mU.mPathPointParams.mPath = aPath; + mU.mPathPointParams.mDistToPoint = aDistToPoint; + + NS_ADDREF(mU.mPathPointParams.mPath); // Retain a reference to path + } + + // Copy constructor (NOTE: AddRef's if we're eSegmentType_PathPoint) + MotionSegment(const MotionSegment& aOther) + : mRotateType(aOther.mRotateType), + mRotateAngle(aOther.mRotateAngle), + mSegmentType(aOther.mSegmentType) { + if (mSegmentType == eSegmentType_Translation) { + mU.mTranslationParams = aOther.mU.mTranslationParams; + } else { // mSegmentType == eSegmentType_PathPoint + mU.mPathPointParams = aOther.mU.mPathPointParams; + NS_ADDREF(mU.mPathPointParams.mPath); // Retain a reference to path + } + } + + // Destructor (releases any reference we were holding onto) + ~MotionSegment() { + if (mSegmentType == eSegmentType_PathPoint) { + NS_RELEASE(mU.mPathPointParams.mPath); + } + } + + // Comparison operators + bool operator==(const MotionSegment& aOther) const { + // Compare basic params + if (mSegmentType != aOther.mSegmentType || + mRotateType != aOther.mRotateType || + (mRotateType == eRotateType_Explicit && // Technically, angle mismatch + mRotateAngle != aOther.mRotateAngle)) { // only matters for Explicit. + return false; + } + + // Compare translation params, if we're a translation. + if (mSegmentType == eSegmentType_Translation) { + return mU.mTranslationParams.mX == aOther.mU.mTranslationParams.mX && + mU.mTranslationParams.mY == aOther.mU.mTranslationParams.mY; + } + + // Else, compare path-point params, if we're a path point. + return (mU.mPathPointParams.mPath == aOther.mU.mPathPointParams.mPath) && + (mU.mPathPointParams.mDistToPoint == + aOther.mU.mPathPointParams.mDistToPoint); + } + + bool operator!=(const MotionSegment& aOther) const { + return !(*this == aOther); + } + + // Member Data + // ----------- + RotateType mRotateType; // Explicit angle vs. auto vs. auto-reverse. + float mRotateAngle; // Only used if mRotateType == eRotateType_Explicit. + const SegmentType mSegmentType; // This determines how we interpret + // mU. (const for safety/sanity) + + union { // Union to let us hold the params for either segment-type. + TranslationParams mTranslationParams; + PathPointParams mPathPointParams; + } mU; +}; + +using MotionSegmentArray = FallibleTArray; + +// Helper methods to cast SMILValue.mU.mPtr to the right pointer-type +static MotionSegmentArray& ExtractMotionSegmentArray(SMILValue& aValue) { + return *static_cast(aValue.mU.mPtr); +} + +static const MotionSegmentArray& ExtractMotionSegmentArray( + const SMILValue& aValue) { + return *static_cast(aValue.mU.mPtr); +} + +// nsISMILType Methods +// ------------------- + +void SVGMotionSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected SMIL type"); + + aValue.mType = this; + aValue.mU.mPtr = new MotionSegmentArray(1); +} + +void SVGMotionSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL type"); + + MotionSegmentArray* arr = static_cast(aValue.mU.mPtr); + delete arr; + + aValue.mU.mPtr = nullptr; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGMotionSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + + const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aSrc); + MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest); + if (!dstArr.Assign(srcArr, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +bool SVGMotionSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected SMIL type"); + + const MotionSegmentArray& leftArr = ExtractMotionSegmentArray(aLeft); + const MotionSegmentArray& rightArr = ExtractMotionSegmentArray(aRight); + + // If array-lengths don't match, we're trivially non-equal. + if (leftArr.Length() != rightArr.Length()) { + return false; + } + + // Array-lengths match -- check each array-entry for equality. + uint32_t length = leftArr.Length(); // == rightArr->Length(), if we get here + for (uint32_t i = 0; i < length; ++i) { + if (leftArr[i] != rightArr[i]) { + return false; + } + } + + return true; // If we get here, we found no differences. +} + +// Helper method for Add & CreateMatrix +inline static void GetAngleAndPointAtDistance( + Path* aPath, float aDistance, RotateType aRotateType, + float& aRotateAngle, // in & out-param. + Point& aPoint) // out-param. +{ + if (aRotateType == eRotateType_Explicit) { + // Leave aRotateAngle as-is. + aPoint = aPath->ComputePointAtLength(aDistance); + } else { + Point tangent; // Unit vector tangent to the point we find. + aPoint = aPath->ComputePointAtLength(aDistance, &tangent); + float tangentAngle = atan2(tangent.y, tangent.x); + if (aRotateType == eRotateType_Auto) { + aRotateAngle = tangentAngle; + } else { + MOZ_ASSERT(aRotateType == eRotateType_AutoReverse); + aRotateAngle = M_PI + tangentAngle; + } + } +} + +nsresult SVGMotionSMILType::Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aDest.mType == aValueToAdd.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + + MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest); + const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aValueToAdd); + + // We're doing a simple add here (as opposed to a sandwich add below). We + // only do this when we're accumulating a repeat result. + // NOTE: In other nsISMILTypes, we use this method with a barely-initialized + // |aDest| value to assist with "by" animation. (In this case, + // "barely-initialized" would mean dstArr.Length() would be empty.) However, + // we don't do this for , because we instead use our "by" + // value to construct an equivalent "path" attribute, and we use *that* for + // our actual animation. + MOZ_ASSERT(srcArr.Length() == 1, "Invalid source segment arr to add"); + MOZ_ASSERT(dstArr.Length() == 1, "Invalid dest segment arr to add to"); + const MotionSegment& srcSeg = srcArr[0]; + const MotionSegment& dstSeg = dstArr[0]; + MOZ_ASSERT(srcSeg.mSegmentType == eSegmentType_PathPoint, + "expecting to be adding points from a motion path"); + MOZ_ASSERT(dstSeg.mSegmentType == eSegmentType_PathPoint, + "expecting to be adding points from a motion path"); + + const PathPointParams& srcParams = srcSeg.mU.mPathPointParams; + const PathPointParams& dstParams = dstSeg.mU.mPathPointParams; + + MOZ_ASSERT(srcSeg.mRotateType == dstSeg.mRotateType && + srcSeg.mRotateAngle == dstSeg.mRotateAngle, + "unexpected angle mismatch"); + MOZ_ASSERT(srcParams.mPath == dstParams.mPath, "unexpected path mismatch"); + Path* path = srcParams.mPath; + + // Use destination to get our rotate angle. + float rotateAngle = dstSeg.mRotateAngle; + Point dstPt; + GetAngleAndPointAtDistance(path, dstParams.mDistToPoint, dstSeg.mRotateType, + rotateAngle, dstPt); + + Point srcPt = path->ComputePointAtLength(srcParams.mDistToPoint); + + float newX = dstPt.x + srcPt.x * aCount; + float newY = dstPt.y + srcPt.y * aCount; + + // Replace destination's current value -- a point-on-a-path -- with the + // translation that results from our addition. + dstArr.ReplaceElementAt(0, MotionSegment(newX, newY, rotateAngle)); + return NS_OK; +} + +nsresult SVGMotionSMILType::SandwichAdd(SMILValue& aDest, + const SMILValue& aValueToAdd) const { + MOZ_ASSERT(aDest.mType == aValueToAdd.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + MotionSegmentArray& dstArr = ExtractMotionSegmentArray(aDest); + const MotionSegmentArray& srcArr = ExtractMotionSegmentArray(aValueToAdd); + + // We're only expecting to be adding 1 segment on to the list + MOZ_ASSERT(srcArr.Length() == 1, + "Trying to do sandwich add of more than one value"); + + if (!dstArr.AppendElement(srcArr[0], fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +nsresult SVGMotionSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == aTo.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aFrom.mType == this, "Unexpected SMIL type"); + const MotionSegmentArray& fromArr = ExtractMotionSegmentArray(aFrom); + const MotionSegmentArray& toArr = ExtractMotionSegmentArray(aTo); + + // ComputeDistance is only used for calculating distances between single + // values in a values array. So we should only have one entry in each array. + MOZ_ASSERT(fromArr.Length() == 1, "Wrong number of elements in from value"); + MOZ_ASSERT(toArr.Length() == 1, "Wrong number of elements in to value"); + + const MotionSegment& from = fromArr[0]; + const MotionSegment& to = toArr[0]; + + MOZ_ASSERT(from.mSegmentType == to.mSegmentType, + "Mismatched MotionSegment types"); + if (from.mSegmentType == eSegmentType_PathPoint) { + const PathPointParams& fromParams = from.mU.mPathPointParams; + const PathPointParams& toParams = to.mU.mPathPointParams; + MOZ_ASSERT(fromParams.mPath == toParams.mPath, + "Interpolation endpoints should be from same path"); + aDistance = std::fabs(toParams.mDistToPoint - fromParams.mDistToPoint); + } else { + const TranslationParams& fromParams = from.mU.mTranslationParams; + const TranslationParams& toParams = to.mU.mTranslationParams; + float dX = toParams.mX - fromParams.mX; + float dY = toParams.mY - fromParams.mY; + aDistance = NS_hypot(dX, dY); + } + + return NS_OK; +} + +// Helper method for Interpolate() +static inline float InterpolateFloat(const float& aStartFlt, + const float& aEndFlt, + const double& aUnitDistance) { + return aStartFlt + aUnitDistance * (aEndFlt - aStartFlt); +} + +nsresult SVGMotionSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + MOZ_ASSERT(aUnitDistance >= 0.0 && aUnitDistance <= 1.0, + "unit distance value out of bounds"); + + const MotionSegmentArray& startArr = ExtractMotionSegmentArray(aStartVal); + const MotionSegmentArray& endArr = ExtractMotionSegmentArray(aEndVal); + MotionSegmentArray& resultArr = ExtractMotionSegmentArray(aResult); + + MOZ_ASSERT(endArr.Length() == 1, + "Invalid end-point for animateMotion interpolation"); + MOZ_ASSERT(resultArr.IsEmpty(), + "Expecting result to be just-initialized w/ empty array"); + + const MotionSegment& endSeg = endArr[0]; + MOZ_ASSERT(endSeg.mSegmentType == eSegmentType_PathPoint, + "Expecting to be interpolating along a path"); + + const PathPointParams& endParams = endSeg.mU.mPathPointParams; + // NOTE: Ususally, path & angle should match between start & end (since + // presumably start & end came from the same element), + // unless start is empty. (as it would be for pure 'to' animation) + // Notable exception: when a to-animation layers on top of lower priority + // animation(s) -- see comment below. + Path* path = endParams.mPath; + RotateType rotateType = endSeg.mRotateType; + float rotateAngle = endSeg.mRotateAngle; + + float startDist; + if (startArr.IsEmpty() || + startArr[0].mU.mPathPointParams.mPath != endParams.mPath) { + // When a to-animation follows other lower priority animation(s), + // the endParams will contain a different path from the animation(s) + // that it layers on top of. + // Per SMIL spec, we should interpolate from the value at startArr. + // However, neither Chrome nor Safari implements to-animation that way. + // For simplicity, we use the same behavior as other browsers: override + // previous animations and start at the initial underlying value. + startDist = 0.0f; + } else { + MOZ_ASSERT(startArr.Length() <= 1, + "Invalid start-point for animateMotion interpolation"); + const MotionSegment& startSeg = startArr[0]; + MOZ_ASSERT(startSeg.mSegmentType == eSegmentType_PathPoint, + "Expecting to be interpolating along a path"); + const PathPointParams& startParams = startSeg.mU.mPathPointParams; + MOZ_ASSERT(startSeg.mRotateType == endSeg.mRotateType && + startSeg.mRotateAngle == endSeg.mRotateAngle, + "unexpected angle mismatch"); + startDist = startParams.mDistToPoint; + } + + // Get the interpolated distance along our path. + float resultDist = + InterpolateFloat(startDist, endParams.mDistToPoint, aUnitDistance); + + // Construct the intermediate result segment, and put it in our outparam. + // AppendElement has guaranteed success here, since Init() allocates 1 slot. + MOZ_ALWAYS_TRUE(resultArr.AppendElement( + MotionSegment(path, resultDist, rotateType, rotateAngle), fallible)); + return NS_OK; +} + +/* static */ gfx::Matrix SVGMotionSMILType::CreateMatrix( + const SMILValue& aSMILVal) { + const MotionSegmentArray& arr = ExtractMotionSegmentArray(aSMILVal); + + gfx::Matrix matrix; + uint32_t length = arr.Length(); + for (uint32_t i = 0; i < length; i++) { + Point point; // initialized below + float rotateAngle = arr[i].mRotateAngle; // might get updated below + if (arr[i].mSegmentType == eSegmentType_Translation) { + point.x = arr[i].mU.mTranslationParams.mX; + point.y = arr[i].mU.mTranslationParams.mY; + MOZ_ASSERT(arr[i].mRotateType == eRotateType_Explicit, + "'auto'/'auto-reverse' should have been converted to " + "explicit angles when we generated this translation"); + } else { + GetAngleAndPointAtDistance(arr[i].mU.mPathPointParams.mPath, + arr[i].mU.mPathPointParams.mDistToPoint, + arr[i].mRotateType, rotateAngle, point); + } + matrix.PreTranslate(point.x, point.y); + matrix.PreRotate(rotateAngle); + } + return matrix; +} + +/* static */ +SMILValue SVGMotionSMILType::ConstructSMILValue(Path* aPath, float aDist, + RotateType aRotateType, + float aRotateAngle) { + SMILValue smilVal(&SVGMotionSMILType::sSingleton); + MotionSegmentArray& arr = ExtractMotionSegmentArray(smilVal); + + // AppendElement has guaranteed success here, since Init() allocates 1 slot. + MOZ_ALWAYS_TRUE(arr.AppendElement( + MotionSegment(aPath, aDist, aRotateType, aRotateAngle), fallible)); + return smilVal; +} + +} // namespace mozilla diff --git a/dom/svg/SVGMotionSMILType.h b/dom/svg/SVGMotionSMILType.h new file mode 100644 index 0000000000..8b62641035 --- /dev/null +++ b/dom/svg/SVGMotionSMILType.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* implementation of SMILType for use by element */ + +#ifndef DOM_SVG_SVGMOTIONSMILTYPE_H_ +#define DOM_SVG_SVGMOTIONSMILTYPE_H_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +namespace mozilla { + +class SMILValue; + +/** + * MotionRotateType: Enum to indicate the type of our "rotate" attribute. + */ +enum RotateType { + eRotateType_Explicit, // for e.g. rotate="45"/"45deg"/"0.785rad" + eRotateType_Auto, // for rotate="auto" + eRotateType_AutoReverse // for rotate="auto-reverse" +}; + +/** + * SVGMotionSMILType: Implements the SMILType interface for SMIL animations + * from . + * + * NOTE: Even though there's technically no "motion" attribute, we behave in + * many ways as if there were, for simplicity. + */ +class SVGMotionSMILType : public SMILType { + using Path = mozilla::gfx::Path; + + public: + // Singleton for SMILValue objects to hold onto. + static SVGMotionSMILType sSingleton; + + protected: + // SMILType Methods + // ------------------- + void Init(SMILValue& aValue) const override; + void Destroy(SMILValue& aValue) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult SandwichAdd(SMILValue& aDest, + const SMILValue& aValueToAdd) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + public: + // Used to generate a transform matrix from an SMILValue. + static gfx::Matrix CreateMatrix(const SMILValue& aSMILVal); + + // Used to generate a SMILValue for the point at the given distance along + // the given path. + static SMILValue ConstructSMILValue(Path* aPath, float aDist, + RotateType aRotateType, + float aRotateAngle); + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGMotionSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGMOTIONSMILTYPE_H_ diff --git a/dom/svg/SVGNumberList.cpp b/dom/svg/SVGNumberList.cpp new file mode 100644 index 0000000000..465379118c --- /dev/null +++ b/dom/svg/SVGNumberList.cpp @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGNumberList.h" + +#include "mozilla/ArrayUtils.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" +#include "nsString.h" +#include "nsTextFormatter.h" +#include "SVGContentUtils.h" + +namespace mozilla { + +nsresult SVGNumberList::CopyFrom(const SVGNumberList& rhs) { + if (!mNumbers.Assign(rhs.mNumbers, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +void SVGNumberList::GetValueAsString(nsAString& aValue) const { + aValue.Truncate(); + char16_t buf[24]; + uint32_t last = mNumbers.Length() - 1; + for (uint32_t i = 0; i < mNumbers.Length(); ++i) { + // Would like to use aValue.AppendPrintf("%f", mNumbers[i]), but it's not + // possible to always avoid trailing zeros. + nsTextFormatter::snprintf(buf, ArrayLength(buf), u"%g", + double(mNumbers[i])); + // We ignore OOM, since it's not useful for us to return an error. + aValue.Append(buf); + if (i != last) { + aValue.Append(' '); + } + } +} + +nsresult SVGNumberList::SetValueFromString(const nsAString& aValue) { + SVGNumberList temp; + + nsCharSeparatedTokenizerTemplate + tokenizer(aValue, ','); + + while (tokenizer.hasMoreTokens()) { + float num; + if (!SVGContentUtils::ParseNumber(tokenizer.nextToken(), num)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + if (!temp.AppendItem(num)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + if (tokenizer.separatorAfterCurrentToken()) { + return NS_ERROR_DOM_SYNTAX_ERR; // trailing comma + } + mNumbers = std::move(temp.mNumbers); + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGNumberList.h b/dom/svg/SVGNumberList.h new file mode 100644 index 0000000000..9f3accb019 --- /dev/null +++ b/dom/svg/SVGNumberList.h @@ -0,0 +1,198 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGNUMBERLIST_H_ +#define DOM_SVG_SVGNUMBERLIST_H_ + +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsIContent.h" +#include "nsINode.h" +#include "nsIWeakReferenceUtils.h" +#include "SVGElement.h" +#include "nsTArray.h" + +namespace mozilla { + +namespace dom { +class DOMSVGNumber; +class DOMSVGNumberList; +} // namespace dom + +/** + * ATTENTION! WARNING! WATCH OUT!! + * + * Consumers that modify objects of this type absolutely MUST keep the DOM + * wrappers for those lists (if any) in sync!! That's why this class is so + * locked down. + * + * The DOM wrapper class for this class is DOMSVGNumberList. + */ +class SVGNumberList { + friend class dom::DOMSVGNumber; + friend class dom::DOMSVGNumberList; + friend class SVGAnimatedNumberList; + + public: + SVGNumberList() = default; + ~SVGNumberList() = default; + + SVGNumberList& operator=(const SVGNumberList& aOther) { + mNumbers.ClearAndRetainStorage(); + // Best-effort, really. + Unused << mNumbers.AppendElements(aOther.mNumbers, fallible); + return *this; + } + + SVGNumberList(const SVGNumberList& aOther) { *this = aOther; } + + // Only methods that don't make/permit modification to this list are public. + // Only our friend classes can access methods that may change us. + + /// This may return an incomplete string on OOM, but that's acceptable. + void GetValueAsString(nsAString& aValue) const; + + bool IsEmpty() const { return mNumbers.IsEmpty(); } + + uint32_t Length() const { return mNumbers.Length(); } + + const float& operator[](uint32_t aIndex) const { return mNumbers[aIndex]; } + + bool operator==(const SVGNumberList& rhs) const { + return mNumbers == rhs.mNumbers; + } + + bool SetCapacity(uint32_t size) { + return mNumbers.SetCapacity(size, fallible); + } + + void Compact() { mNumbers.Compact(); } + + // Access to methods that can modify objects of this type is deliberately + // limited. This is to reduce the chances of someone modifying objects of + // this type without taking the necessary steps to keep DOM wrappers in sync. + // If you need wider access to these methods, consider adding a method to + // SVGAnimatedNumberList and having that class act as an intermediary so it + // can take care of keeping DOM wrappers in sync. + + protected: + /** + * This may fail on OOM if the internal capacity needs to be increased, in + * which case the list will be left unmodified. + */ + nsresult CopyFrom(const SVGNumberList& rhs); + void SwapWith(SVGNumberList& aRhs) { mNumbers.SwapElements(aRhs.mNumbers); } + + float& operator[](uint32_t aIndex) { return mNumbers[aIndex]; } + + /** + * This may fail (return false) on OOM if the internal capacity is being + * increased, in which case the list will be left unmodified. + */ + bool SetLength(uint32_t aNumberOfItems) { + return mNumbers.SetLength(aNumberOfItems, fallible); + } + + private: + // Marking the following private only serves to show which methods are only + // used by our friend classes (as opposed to our subclasses) - it doesn't + // really provide additional safety. + + nsresult SetValueFromString(const nsAString& aValue); + + void Clear() { mNumbers.Clear(); } + + bool InsertItem(uint32_t aIndex, const float& aNumber) { + if (aIndex >= mNumbers.Length()) { + aIndex = mNumbers.Length(); + } + return !!mNumbers.InsertElementAt(aIndex, aNumber, fallible); + } + + void ReplaceItem(uint32_t aIndex, const float& aNumber) { + MOZ_ASSERT(aIndex < mNumbers.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mNumbers[aIndex] = aNumber; + } + + void RemoveItem(uint32_t aIndex) { + MOZ_ASSERT(aIndex < mNumbers.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mNumbers.RemoveElementAt(aIndex); + } + + bool AppendItem(float aNumber) { + return !!mNumbers.AppendElement(aNumber, fallible); + } + + protected: + /* See SVGLengthList for the rationale for using FallibleTArray instead + * of FallibleTArray. + */ + FallibleTArray mNumbers; +}; + +/** + * This SVGNumberList subclass is used by the SMIL code when a number list + * is to be stored in a SMILValue instance. Since SMILValue objects may + * be cached, it is necessary for us to hold a strong reference to our element + * so that it doesn't disappear out from under us if, say, the element is + * removed from the DOM tree. + */ +class SVGNumberListAndInfo : public SVGNumberList { + public: + SVGNumberListAndInfo() : mElement(nullptr) {} + + explicit SVGNumberListAndInfo(dom::SVGElement* aElement) + : mElement(do_GetWeakReference(static_cast(aElement))) {} + + void SetInfo(dom::SVGElement* aElement) { + mElement = do_GetWeakReference(static_cast(aElement)); + } + + dom::SVGElement* Element() const { + nsCOMPtr e = do_QueryReferent(mElement); + return static_cast(e.get()); + } + + nsresult CopyFrom(const SVGNumberListAndInfo& rhs) { + mElement = rhs.mElement; + return SVGNumberList::CopyFrom(rhs); + } + + // Instances of this special subclass do not have DOM wrappers that we need + // to worry about keeping in sync, so it's safe to expose any hidden base + // class methods required by the SMIL code, as we do below. + + /** + * Exposed so that SVGNumberList baseVals can be copied to + * SVGNumberListAndInfo objects. Note that callers should also call + * SetInfo() when using this method! + */ + nsresult CopyFrom(const SVGNumberList& rhs) { + return SVGNumberList::CopyFrom(rhs); + } + const float& operator[](uint32_t aIndex) const { + return SVGNumberList::operator[](aIndex); + } + float& operator[](uint32_t aIndex) { + return SVGNumberList::operator[](aIndex); + } + bool SetLength(uint32_t aNumberOfItems) { + return SVGNumberList::SetLength(aNumberOfItems); + } + + private: + // We must keep a weak reference to our element because we may belong to a + // cached baseVal SMILValue. See the comments starting at: + // https://bugzilla.mozilla.org/show_bug.cgi?id=515116#c15 + // See also https://bugzilla.mozilla.org/show_bug.cgi?id=653497 + nsWeakPtr mElement; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGNUMBERLIST_H_ diff --git a/dom/svg/SVGNumberListSMILType.cpp b/dom/svg/SVGNumberListSMILType.cpp new file mode 100644 index 0000000000..e087d46379 --- /dev/null +++ b/dom/svg/SVGNumberListSMILType.cpp @@ -0,0 +1,205 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGNumberListSMILType.h" + +#include "mozilla/FloatingPoint.h" +#include "mozilla/SMILValue.h" +#include "nsMathUtils.h" +#include "SVGNumberList.h" +#include + +/* The "identity" number list for a given number list attribute (the effective + * number list that is used if an attribute value is not specified) varies + * widely for different number list attributes, and can depend on the value of + * other attributes on the same element: + * + * http://www.w3.org/TR/SVG11/filters.html#feColorMatrixValuesAttribute + * + http://www.w3.org/TR/SVG11/filters.html#feComponentTransferTableValuesAttribute + * + http://www.w3.org/TR/SVG11/filters.html#feConvolveMatrixElementKernelMatrixAttribute + * http://www.w3.org/TR/SVG11/text.html#TextElementRotateAttribute + * + * Note that we don't need to worry about that variation here, however. The way + * that the SMIL engine creates and composites sandwich layers together allows + * us to treat "identity" SMILValue objects as a number list of zeros. Such + * identity SMILValues are identified by the fact that their + # SVGNumberListAndInfo has not been given an element yet. + */ + +namespace mozilla { + +/*static*/ +SVGNumberListSMILType SVGNumberListSMILType::sSingleton; + +//---------------------------------------------------------------------- +// nsISMILType implementation + +void SVGNumberListSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + + SVGNumberListAndInfo* numberList = new SVGNumberListAndInfo(); + + aValue.mU.mPtr = numberList; + aValue.mType = this; +} + +void SVGNumberListSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value type"); + delete static_cast(aValue.mU.mPtr); + aValue.mU.mPtr = nullptr; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGNumberListSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value"); + + const SVGNumberListAndInfo* src = + static_cast(aSrc.mU.mPtr); + SVGNumberListAndInfo* dest = + static_cast(aDest.mU.mPtr); + + return dest->CopyFrom(*src); +} + +bool SVGNumberListSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected type for SMIL value"); + + return *static_cast(aLeft.mU.mPtr) == + *static_cast(aRight.mU.mPtr); +} + +nsresult SVGNumberListSMILType::Add(SMILValue& aDest, + const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aValueToAdd.mType == this, "Incompatible SMIL type"); + + SVGNumberListAndInfo& dest = + *static_cast(aDest.mU.mPtr); + const SVGNumberListAndInfo& valueToAdd = + *static_cast(aValueToAdd.mU.mPtr); + + MOZ_ASSERT(dest.Element() || valueToAdd.Element(), + "Target element propagation failure"); + + if (!valueToAdd.Element()) { + MOZ_ASSERT(valueToAdd.Length() == 0, + "Not identity value - target element propagation failure"); + return NS_OK; + } + if (!dest.Element()) { + MOZ_ASSERT(dest.Length() == 0, + "Not identity value - target element propagation failure"); + if (!dest.SetLength(valueToAdd.Length())) { + return NS_ERROR_OUT_OF_MEMORY; + } + for (uint32_t i = 0; i < dest.Length(); ++i) { + dest[i] = aCount * valueToAdd[i]; + } + dest.SetInfo(valueToAdd.Element()); // propagate target element info! + return NS_OK; + } + MOZ_ASSERT(dest.Element() == valueToAdd.Element(), + "adding values from different elements...?"); + if (dest.Length() != valueToAdd.Length()) { + // For now we only support animation between lists with the same number of + // items. SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + for (uint32_t i = 0; i < dest.Length(); ++i) { + dest[i] += aCount * valueToAdd[i]; + } + dest.SetInfo(valueToAdd.Element()); // propagate target element info! + return NS_OK; +} + +nsresult SVGNumberListSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aTo.mType == this, "Incompatible SMIL type"); + + const SVGNumberListAndInfo& from = + *static_cast(aFrom.mU.mPtr); + const SVGNumberListAndInfo& to = + *static_cast(aTo.mU.mPtr); + + if (from.Length() != to.Length()) { + // Lists in the 'values' attribute must have the same length. + // SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + + // We return the root of the sum of the squares of the delta between the + // numbers at each correspanding index. + + double total = 0.0; + + for (uint32_t i = 0; i < to.Length(); ++i) { + double delta = to[i] - from[i]; + total += delta * delta; + } + double distance = sqrt(total); + if (!std::isfinite(distance)) { + return NS_ERROR_FAILURE; + } + aDistance = distance; + + return NS_OK; +} + +nsresult SVGNumberListSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + + const SVGNumberListAndInfo& start = + *static_cast(aStartVal.mU.mPtr); + const SVGNumberListAndInfo& end = + *static_cast(aEndVal.mU.mPtr); + SVGNumberListAndInfo& result = + *static_cast(aResult.mU.mPtr); + + MOZ_ASSERT(end.Element(), "Can't propagate target element"); + MOZ_ASSERT(start.Element() == end.Element() || !start.Element(), + "Different target elements"); + + if (start.Element() && // 'start' is not an "identity" value + start.Length() != end.Length()) { + // For now we only support animation between lists of the same length. + // SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + if (!result.SetLength(end.Length())) { + return NS_ERROR_OUT_OF_MEMORY; + } + + result.SetInfo(end.Element()); // propagate target element info! + + if (start.Length() != end.Length()) { + MOZ_ASSERT(start.Length() == 0, "Not an identity value"); + for (uint32_t i = 0; i < end.Length(); ++i) { + result[i] = aUnitDistance * end[i]; + } + return NS_OK; + } + for (uint32_t i = 0; i < end.Length(); ++i) { + result[i] = start[i] + (end[i] - start[i]) * aUnitDistance; + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGNumberListSMILType.h b/dom/svg/SVGNumberListSMILType.h new file mode 100644 index 0000000000..1ba803fc10 --- /dev/null +++ b/dom/svg/SVGNumberListSMILType.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGNUMBERLISTSMILTYPE_H_ +#define DOM_SVG_SVGNUMBERLISTSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +namespace mozilla { + +class SMILValue; + +//////////////////////////////////////////////////////////////////////// +// SVGNumberListSMILType +// +// Operations for animating an SVGNumberList. +// +class SVGNumberListSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGNumberListSMILType sSingleton; + + protected: + // SMILType Methods + // ------------------- + + void Init(SMILValue& aValue) const override; + + void Destroy(SMILValue& aValue) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGNumberListSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGNUMBERLISTSMILTYPE_H_ diff --git a/dom/svg/SVGNumberPairSMILType.cpp b/dom/svg/SVGNumberPairSMILType.cpp new file mode 100644 index 0000000000..f0c6a61862 --- /dev/null +++ b/dom/svg/SVGNumberPairSMILType.cpp @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGNumberPairSMILType.h" + +#include "mozilla/SMILValue.h" +#include "nsMathUtils.h" +#include "nsDebug.h" + +namespace mozilla { + +/*static*/ +SVGNumberPairSMILType SVGNumberPairSMILType::sSingleton; + +void SVGNumberPairSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + + aValue.mU.mNumberPair[0] = 0; + aValue.mU.mNumberPair[1] = 0; + aValue.mType = this; +} + +void SVGNumberPairSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value"); + aValue.mU.mNumberPair[0] = 0; + aValue.mU.mNumberPair[1] = 0; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGNumberPairSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value"); + + aDest.mU.mNumberPair[0] = aSrc.mU.mNumberPair[0]; + aDest.mU.mNumberPair[1] = aSrc.mU.mNumberPair[1]; + return NS_OK; +} + +bool SVGNumberPairSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected type for SMIL value"); + + return aLeft.mU.mNumberPair[0] == aRight.mU.mNumberPair[0] && + aLeft.mU.mNumberPair[1] == aRight.mU.mNumberPair[1]; +} + +nsresult SVGNumberPairSMILType::Add(SMILValue& aDest, + const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aValueToAdd.mType == aDest.mType, "Trying to add invalid types"); + MOZ_ASSERT(aValueToAdd.mType == this, "Unexpected source type"); + + aDest.mU.mNumberPair[0] += aValueToAdd.mU.mNumberPair[0] * aCount; + aDest.mU.mNumberPair[1] += aValueToAdd.mU.mNumberPair[1] * aCount; + + return NS_OK; +} + +nsresult SVGNumberPairSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == aTo.mType, "Trying to compare different types"); + MOZ_ASSERT(aFrom.mType == this, "Unexpected source type"); + + double delta[2]; + delta[0] = aTo.mU.mNumberPair[0] - aFrom.mU.mNumberPair[0]; + delta[1] = aTo.mU.mNumberPair[1] - aFrom.mU.mNumberPair[1]; + + aDistance = NS_hypot(delta[0], delta[1]); + return NS_OK; +} + +nsresult SVGNumberPairSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + + aResult.mU.mNumberPair[0] = + float(aStartVal.mU.mNumberPair[0] + + (aEndVal.mU.mNumberPair[0] - aStartVal.mU.mNumberPair[0]) * + aUnitDistance); + aResult.mU.mNumberPair[1] = + float(aStartVal.mU.mNumberPair[1] + + (aEndVal.mU.mNumberPair[1] - aStartVal.mU.mNumberPair[1]) * + aUnitDistance); + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGNumberPairSMILType.h b/dom/svg/SVGNumberPairSMILType.h new file mode 100644 index 0000000000..7e9cce5f73 --- /dev/null +++ b/dom/svg/SVGNumberPairSMILType.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGNUMBERPAIRSMILTYPE_H_ +#define DOM_SVG_SVGNUMBERPAIRSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +namespace mozilla { + +class SMILValue; + +class SVGNumberPairSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGNumberPairSMILType sSingleton; + + protected: + // SMILType Methods + // ------------------- + void Init(SMILValue& aValue) const override; + void Destroy(SMILValue&) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGNumberPairSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGNUMBERPAIRSMILTYPE_H_ diff --git a/dom/svg/SVGOrientSMILType.cpp b/dom/svg/SVGOrientSMILType.cpp new file mode 100644 index 0000000000..1050c7a763 --- /dev/null +++ b/dom/svg/SVGOrientSMILType.cpp @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGOrientSMILType.h" + +#include "mozilla/SMILValue.h" +#include "mozilla/dom/SVGMarkerElement.h" +#include "nsDebug.h" +#include "SVGAnimatedOrient.h" +#include + +namespace mozilla { + +using namespace dom::SVGAngle_Binding; +using namespace dom::SVGMarkerElement_Binding; + +/*static*/ +SVGOrientSMILType SVGOrientSMILType::sSingleton; + +void SVGOrientSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + + aValue.mU.mOrient.mAngle = 0.0f; + aValue.mU.mOrient.mUnit = SVG_ANGLETYPE_UNSPECIFIED; + aValue.mU.mOrient.mOrientType = SVG_MARKER_ORIENT_ANGLE; + aValue.mType = this; +} + +void SVGOrientSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value."); + aValue.mU.mPtr = nullptr; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGOrientSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types."); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value."); + + aDest.mU.mOrient.mAngle = aSrc.mU.mOrient.mAngle; + aDest.mU.mOrient.mUnit = aSrc.mU.mOrient.mUnit; + aDest.mU.mOrient.mOrientType = aSrc.mU.mOrient.mOrientType; + return NS_OK; +} + +bool SVGOrientSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected type for SMIL value"); + + return aLeft.mU.mOrient.mAngle == aRight.mU.mOrient.mAngle && + aLeft.mU.mOrient.mUnit == aRight.mU.mOrient.mUnit && + aLeft.mU.mOrient.mOrientType == aRight.mU.mOrient.mOrientType; +} + +nsresult SVGOrientSMILType::Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aValueToAdd.mType == aDest.mType, "Trying to add invalid types"); + MOZ_ASSERT(aValueToAdd.mType == this, "Unexpected source type"); + + if (aDest.mU.mOrient.mOrientType != SVG_MARKER_ORIENT_ANGLE || + aValueToAdd.mU.mOrient.mOrientType != SVG_MARKER_ORIENT_ANGLE) { + // TODO: it would be nice to be able to add to auto angles + return NS_ERROR_FAILURE; + } + + // We may be dealing with two different angle units, so we normalize to + // degrees for the add: + float currentAngle = + aDest.mU.mOrient.mAngle * + SVGAnimatedOrient::GetDegreesPerUnit(aDest.mU.mOrient.mUnit); + float angleToAdd = + aValueToAdd.mU.mOrient.mAngle * + SVGAnimatedOrient::GetDegreesPerUnit(aValueToAdd.mU.mOrient.mUnit) * + aCount; + + // And then we give the resulting animated value the same units as the value + // that we're animating to/by (i.e. the same as aValueToAdd): + aDest.mU.mOrient.mAngle = + (currentAngle + angleToAdd) / + SVGAnimatedOrient::GetDegreesPerUnit(aValueToAdd.mU.mOrient.mUnit); + aDest.mU.mOrient.mUnit = aValueToAdd.mU.mOrient.mUnit; + + return NS_OK; +} + +nsresult SVGOrientSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == aTo.mType, "Trying to compare different types"); + MOZ_ASSERT(aFrom.mType == this, "Unexpected source type"); + + if (aFrom.mU.mOrient.mOrientType != SVG_MARKER_ORIENT_ANGLE || + aTo.mU.mOrient.mOrientType != SVG_MARKER_ORIENT_ANGLE) { + // TODO: it would be nice to be able to compute distance with auto angles + return NS_ERROR_FAILURE; + } + + // Normalize both to degrees in case they're different angle units: + double from = aFrom.mU.mOrient.mAngle * + SVGAnimatedOrient::GetDegreesPerUnit(aFrom.mU.mOrient.mUnit); + double to = aTo.mU.mOrient.mAngle * + SVGAnimatedOrient::GetDegreesPerUnit(aTo.mU.mOrient.mUnit); + + aDistance = fabs(to - from); + + return NS_OK; +} + +nsresult SVGOrientSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation."); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type."); + + if (aStartVal.mU.mOrient.mOrientType != SVG_MARKER_ORIENT_ANGLE || + aEndVal.mU.mOrient.mOrientType != SVG_MARKER_ORIENT_ANGLE) { + // TODO: it would be nice to be able to handle auto angles too. + return NS_ERROR_FAILURE; + } + + float start = + aStartVal.mU.mOrient.mAngle * + SVGAnimatedOrient::GetDegreesPerUnit(aStartVal.mU.mOrient.mUnit); + float end = aEndVal.mU.mOrient.mAngle * + SVGAnimatedOrient::GetDegreesPerUnit(aEndVal.mU.mOrient.mUnit); + float result = (start + (end - start) * aUnitDistance); + + // Again, we use the unit of the to/by value for the result: + aResult.mU.mOrient.mAngle = + result / SVGAnimatedOrient::GetDegreesPerUnit(aEndVal.mU.mOrient.mUnit); + aResult.mU.mOrient.mUnit = aEndVal.mU.mOrient.mUnit; + + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGOrientSMILType.h b/dom/svg/SVGOrientSMILType.h new file mode 100644 index 0000000000..01ca03251a --- /dev/null +++ b/dom/svg/SVGOrientSMILType.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGORIENTSMILTYPE_H_ +#define DOM_SVG_SVGORIENTSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +/** + * This SMILType class is a special case for the 'orient' attribute on SVG's + * 'marker' element. + * + * orient = "auto | auto-start-reverse | " + * + * Unusually, this attribute doesn't have just a single corresponding DOM + * property, but rather is split into two properties: 'orientType' (of type + * DOMSVGAnimatedEnumeration) and 'orientAngle' (of type DOMSVGAnimatedAngle). + * If 'orientType.animVal' is SVG_MARKER_ORIENT_ANGLE, then + * 'orientAngle.animVal' contains the angle that is being used. The lacuna + * value is 0. + */ + +namespace mozilla { + +class SMILValue; + +class SVGOrientSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGOrientSMILType sSingleton; + + protected: + // SMILType Methods + // ------------------- + void Init(SMILValue& aValue) const override; + void Destroy(SMILValue&) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGOrientSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGORIENTSMILTYPE_H_ diff --git a/dom/svg/SVGPathData.cpp b/dom/svg/SVGPathData.cpp new file mode 100644 index 0000000000..4dde07fcbe --- /dev/null +++ b/dom/svg/SVGPathData.cpp @@ -0,0 +1,1357 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGPathData.h" + +#include "gfx2DGlue.h" +#include "gfxPlatform.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/RefPtr.h" +#include "nsError.h" +#include "nsString.h" +#include "SVGPathDataParser.h" +#include +#include "nsStyleConsts.h" +#include "SVGContentUtils.h" +#include "SVGGeometryElement.h" +#include "SVGPathSegUtils.h" +#include + +using namespace mozilla::dom::SVGPathSeg_Binding; +using namespace mozilla::gfx; + +namespace mozilla { + +static inline bool IsMoveto(uint16_t aSegType) { + return aSegType == PATHSEG_MOVETO_ABS || aSegType == PATHSEG_MOVETO_REL; +} + +static inline bool IsMoveto(StylePathCommand::Tag aSegType) { + return aSegType == StylePathCommand::Tag::MoveTo; +} + +static inline bool IsValidType(uint16_t aSegType) { + return SVGPathSegUtils::IsValidType(aSegType); +} + +static inline bool IsValidType(StylePathCommand::Tag aSegType) { + return aSegType != StylePathCommand::Tag::Unknown; +} + +static inline bool IsClosePath(uint16_t aSegType) { + return aSegType == PATHSEG_CLOSEPATH; +} + +static inline bool IsClosePath(StylePathCommand::Tag aSegType) { + return aSegType == StylePathCommand::Tag::ClosePath; +} + +static inline bool IsCubicType(StylePathCommand::Tag aType) { + return aType == StylePathCommand::Tag::CurveTo || + aType == StylePathCommand::Tag::SmoothCurveTo; +} + +static inline bool IsQuadraticType(StylePathCommand::Tag aType) { + return aType == StylePathCommand::Tag::QuadBezierCurveTo || + aType == StylePathCommand::Tag::SmoothQuadBezierCurveTo; +} + +nsresult SVGPathData::CopyFrom(const SVGPathData& rhs) { + if (!mData.Assign(rhs.mData, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +void SVGPathData::GetValueAsString(nsAString& aValue) const { + // we need this function in DidChangePathSegList + aValue.Truncate(); + if (!Length()) { + return; + } + uint32_t i = 0; + for (;;) { + nsAutoString segAsString; + SVGPathSegUtils::GetValueAsString(&mData[i], segAsString); + // We ignore OOM, since it's not useful for us to return an error. + aValue.Append(segAsString); + i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]); + if (i >= mData.Length()) { + MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); + return; + } + aValue.Append(' '); + } +} + +nsresult SVGPathData::SetValueFromString(const nsAString& aValue) { + // We don't use a temp variable since the spec says to parse everything up to + // the first error. We still return any error though so that callers know if + // there's a problem. + + SVGPathDataParser pathParser(aValue, this); + return pathParser.Parse() ? NS_OK : NS_ERROR_DOM_SYNTAX_ERR; +} + +nsresult SVGPathData::AppendSeg(uint32_t aType, ...) { + uint32_t oldLength = mData.Length(); + uint32_t newLength = oldLength + 1 + SVGPathSegUtils::ArgCountForType(aType); + if (!mData.SetLength(newLength, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mData[oldLength] = SVGPathSegUtils::EncodeType(aType); + va_list args; + va_start(args, aType); + for (uint32_t i = oldLength + 1; i < newLength; ++i) { + // NOTE! 'float' is promoted to 'double' when passed through '...'! + mData[i] = float(va_arg(args, double)); + } + va_end(args); + return NS_OK; +} + +float SVGPathData::GetPathLength() const { + SVGPathTraversalState state; + + uint32_t i = 0; + while (i < mData.Length()) { + SVGPathSegUtils::TraversePathSegment(&mData[i], state); + i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]); + } + + MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); + + return state.length; +} + +#ifdef DEBUG +uint32_t SVGPathData::CountItems() const { + uint32_t i = 0, count = 0; + + while (i < mData.Length()) { + i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]); + count++; + } + + MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); + + return count; +} +#endif + +bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments( + FallibleTArray* aOutput) const { + SVGPathTraversalState state; + + aOutput->Clear(); + + uint32_t i = 0; + while (i < mData.Length()) { + uint32_t segType = SVGPathSegUtils::DecodeType(mData[i]); + SVGPathSegUtils::TraversePathSegment(&mData[i], state); + + // With degenerately large point coordinates, TraversePathSegment can fail + // and end up producing NaNs. + if (!std::isfinite(state.length)) { + return false; + } + + // We skip all moveto commands except an initial moveto. See the text 'A + // "move to" command does not count as an additional point when dividing up + // the duration...': + // + // http://www.w3.org/TR/SVG11/animate.html#AnimateMotionElement + // + // This is important in the non-default case of calcMode="linear". In + // this case an equal amount of time is spent on each path segment, + // except on moveto segments which are jumped over immediately. + + if (i == 0 || !IsMoveto(segType)) { + if (!aOutput->AppendElement(state.length, fallible)) { + return false; + } + } + i += 1 + SVGPathSegUtils::ArgCountForType(segType); + } + + MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt?"); + + return true; +} + +/* static */ +bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments( + Span aPath, FallibleTArray* aOutput) { + SVGPathTraversalState state; + + aOutput->Clear(); + + bool firstMoveToIsChecked = false; + for (const auto& cmd : aPath) { + SVGPathSegUtils::TraversePathSegment(cmd, state); + if (!std::isfinite(state.length)) { + return false; + } + + // We skip all moveto commands except for the initial moveto. + if (!cmd.IsMoveTo() || !firstMoveToIsChecked) { + if (!aOutput->AppendElement(state.length, fallible)) { + return false; + } + } + + if (cmd.IsMoveTo() && !firstMoveToIsChecked) { + firstMoveToIsChecked = true; + } + } + + return true; +} + +uint32_t SVGPathData::GetPathSegAtLength(float aDistance) const { + // TODO [SVGWG issue] get specified what happen if 'aDistance' < 0, or + // 'aDistance' > the length of the path, or the seg list is empty. + // Return -1? Throwing would better help authors avoid tricky bugs (DOM + // could do that if we return -1). + + uint32_t i = 0, segIndex = 0; + SVGPathTraversalState state; + + while (i < mData.Length()) { + SVGPathSegUtils::TraversePathSegment(&mData[i], state); + if (state.length >= aDistance) { + return segIndex; + } + i += 1 + SVGPathSegUtils::ArgCountForType(mData[i]); + segIndex++; + } + + MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); + + return std::max(1U, segIndex) - + 1; // -1 because while loop takes us 1 too far +} + +/* static */ +uint32_t SVGPathData::GetPathSegAtLength(Span aPath, + float aDistance) { + uint32_t segIndex = 0; + SVGPathTraversalState state; + + for (const auto& cmd : aPath) { + SVGPathSegUtils::TraversePathSegment(cmd, state); + if (state.length >= aDistance) { + return segIndex; + } + segIndex++; + } + + return std::max(1U, segIndex) - 1; +} + +/** + * The SVG spec says we have to paint stroke caps for zero length subpaths: + * + * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes + * + * Cairo only does this for |stroke-linecap: round| and not for + * |stroke-linecap: square| (since that's what Adobe Acrobat has always done). + * Most likely the other backends that DrawTarget uses have the same behavior. + * + * To help us conform to the SVG spec we have this helper function to draw an + * approximation of square caps for zero length subpaths. It does this by + * inserting a subpath containing a single user space axis aligned straight + * line that is as small as it can be while minimizing the risk of it being + * thrown away by the DrawTarget's backend for being too small to affect + * rendering. The idea is that we'll then get stroke caps drawn for this axis + * aligned line, creating an axis aligned rectangle that approximates the + * square that would ideally be drawn. + * + * Since we don't have any information about transforms from user space to + * device space, we choose the length of the small line that we insert by + * making it a small percentage of the stroke width of the path. This should + * hopefully allow us to make the line as long as possible (to avoid rounding + * issues in the backend resulting in the backend seeing it as having zero + * length) while still avoiding the small rectangle being noticeably different + * from a square. + * + * Note that this function inserts a subpath into the current gfx path that + * will be present during both fill and stroke operations. + */ +static void ApproximateZeroLengthSubpathSquareCaps(PathBuilder* aPB, + const Point& aPoint, + Float aStrokeWidth) { + // Note that caps are proportional to stroke width, so if stroke width is + // zero it's actually fine for |tinyLength| below to end up being zero. + // However, it would be a waste to inserting a LineTo in that case, so better + // not to. + MOZ_ASSERT(aStrokeWidth > 0.0f, + "Make the caller check for this, or check it here"); + + // The fraction of the stroke width that we choose for the length of the + // line is rather arbitrary, other than being chosen to meet the requirements + // described in the comment above. + + Float tinyLength = aStrokeWidth / SVG_ZERO_LENGTH_PATH_FIX_FACTOR; + + aPB->LineTo(aPoint + Point(tinyLength, 0)); + aPB->MoveTo(aPoint); +} + +#define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT \ + do { \ + if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 && \ + subpathContainsNonMoveTo && IsValidType(prevSegType) && \ + (!IsMoveto(prevSegType) || IsClosePath(segType))) { \ + ApproximateZeroLengthSubpathSquareCaps(aBuilder, segStart, \ + aStrokeWidth); \ + } \ + } while (0) + +already_AddRefed SVGPathData::BuildPath(PathBuilder* aBuilder, + StyleStrokeLinecap aStrokeLineCap, + Float aStrokeWidth) const { + if (mData.IsEmpty() || !IsMoveto(SVGPathSegUtils::DecodeType(mData[0]))) { + return nullptr; // paths without an initial moveto are invalid + } + + bool hasLineCaps = aStrokeLineCap != StyleStrokeLinecap::Butt; + bool subpathHasLength = false; // visual length + bool subpathContainsNonMoveTo = false; + + uint32_t segType = PATHSEG_UNKNOWN; + uint32_t prevSegType = PATHSEG_UNKNOWN; + Point pathStart(0.0, 0.0); // start point of [sub]path + Point segStart(0.0, 0.0); + Point segEnd; + Point cp1, cp2; // previous bezier's control points + Point tcp1, tcp2; // temporaries + + // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve, + // then cp2 is its second control point. If the previous segment was a + // quadratic curve, then cp1 is its (only) control point. + + uint32_t i = 0; + while (i < mData.Length()) { + segType = SVGPathSegUtils::DecodeType(mData[i++]); + uint32_t argCount = SVGPathSegUtils::ArgCountForType(segType); + + switch (segType) { + case PATHSEG_CLOSEPATH: + // set this early to allow drawing of square caps for "M{x},{y} Z": + subpathContainsNonMoveTo = true; + MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; + segEnd = pathStart; + aBuilder->Close(); + break; + + case PATHSEG_MOVETO_ABS: + MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; + pathStart = segEnd = Point(mData[i], mData[i + 1]); + aBuilder->MoveTo(segEnd); + subpathHasLength = false; + break; + + case PATHSEG_MOVETO_REL: + MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; + pathStart = segEnd = segStart + Point(mData[i], mData[i + 1]); + aBuilder->MoveTo(segEnd); + subpathHasLength = false; + break; + + case PATHSEG_LINETO_ABS: + segEnd = Point(mData[i], mData[i + 1]); + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(segEnd); + } + break; + + case PATHSEG_LINETO_REL: + segEnd = segStart + Point(mData[i], mData[i + 1]); + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(segEnd); + } + break; + + case PATHSEG_CURVETO_CUBIC_ABS: + cp1 = Point(mData[i], mData[i + 1]); + cp2 = Point(mData[i + 2], mData[i + 3]); + segEnd = Point(mData[i + 4], mData[i + 5]); + if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { + subpathHasLength = true; + aBuilder->BezierTo(cp1, cp2, segEnd); + } + break; + + case PATHSEG_CURVETO_CUBIC_REL: + cp1 = segStart + Point(mData[i], mData[i + 1]); + cp2 = segStart + Point(mData[i + 2], mData[i + 3]); + segEnd = segStart + Point(mData[i + 4], mData[i + 5]); + if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { + subpathHasLength = true; + aBuilder->BezierTo(cp1, cp2, segEnd); + } + break; + + case PATHSEG_CURVETO_QUADRATIC_ABS: + cp1 = Point(mData[i], mData[i + 1]); + // Convert quadratic curve to cubic curve: + tcp1 = segStart + (cp1 - segStart) * 2 / 3; + segEnd = Point(mData[i + 2], mData[i + 3]); // set before setting tcp2! + tcp2 = cp1 + (segEnd - cp1) / 3; + if (segEnd != segStart || segEnd != cp1) { + subpathHasLength = true; + aBuilder->BezierTo(tcp1, tcp2, segEnd); + } + break; + + case PATHSEG_CURVETO_QUADRATIC_REL: + cp1 = segStart + Point(mData[i], mData[i + 1]); + // Convert quadratic curve to cubic curve: + tcp1 = segStart + (cp1 - segStart) * 2 / 3; + segEnd = segStart + + Point(mData[i + 2], mData[i + 3]); // set before setting tcp2! + tcp2 = cp1 + (segEnd - cp1) / 3; + if (segEnd != segStart || segEnd != cp1) { + subpathHasLength = true; + aBuilder->BezierTo(tcp1, tcp2, segEnd); + } + break; + + case PATHSEG_ARC_ABS: + case PATHSEG_ARC_REL: { + Point radii(mData[i], mData[i + 1]); + segEnd = Point(mData[i + 5], mData[i + 6]); + if (segType == PATHSEG_ARC_REL) { + segEnd += segStart; + } + if (segEnd != segStart) { + subpathHasLength = true; + if (radii.x == 0.0f || radii.y == 0.0f) { + aBuilder->LineTo(segEnd); + } else { + SVGArcConverter converter(segStart, segEnd, radii, mData[i + 2], + mData[i + 3] != 0, mData[i + 4] != 0); + while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) { + aBuilder->BezierTo(cp1, cp2, segEnd); + } + } + } + break; + } + + case PATHSEG_LINETO_HORIZONTAL_ABS: + segEnd = Point(mData[i], segStart.y); + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(segEnd); + } + break; + + case PATHSEG_LINETO_HORIZONTAL_REL: + segEnd = segStart + Point(mData[i], 0.0f); + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(segEnd); + } + break; + + case PATHSEG_LINETO_VERTICAL_ABS: + segEnd = Point(segStart.x, mData[i]); + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(segEnd); + } + break; + + case PATHSEG_LINETO_VERTICAL_REL: + segEnd = segStart + Point(0.0f, mData[i]); + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(segEnd); + } + break; + + case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS: + cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2 + : segStart; + cp2 = Point(mData[i], mData[i + 1]); + segEnd = Point(mData[i + 2], mData[i + 3]); + if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { + subpathHasLength = true; + aBuilder->BezierTo(cp1, cp2, segEnd); + } + break; + + case PATHSEG_CURVETO_CUBIC_SMOOTH_REL: + cp1 = SVGPathSegUtils::IsCubicType(prevSegType) ? segStart * 2 - cp2 + : segStart; + cp2 = segStart + Point(mData[i], mData[i + 1]); + segEnd = segStart + Point(mData[i + 2], mData[i + 3]); + if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { + subpathHasLength = true; + aBuilder->BezierTo(cp1, cp2, segEnd); + } + break; + + case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS: + cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1 + : segStart; + // Convert quadratic curve to cubic curve: + tcp1 = segStart + (cp1 - segStart) * 2 / 3; + segEnd = Point(mData[i], mData[i + 1]); // set before setting tcp2! + tcp2 = cp1 + (segEnd - cp1) / 3; + if (segEnd != segStart || segEnd != cp1) { + subpathHasLength = true; + aBuilder->BezierTo(tcp1, tcp2, segEnd); + } + break; + + case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL: + cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) ? segStart * 2 - cp1 + : segStart; + // Convert quadratic curve to cubic curve: + tcp1 = segStart + (cp1 - segStart) * 2 / 3; + segEnd = segStart + + Point(mData[i], mData[i + 1]); // changed before setting tcp2! + tcp2 = cp1 + (segEnd - cp1) / 3; + if (segEnd != segStart || segEnd != cp1) { + subpathHasLength = true; + aBuilder->BezierTo(tcp1, tcp2, segEnd); + } + break; + + default: + MOZ_ASSERT_UNREACHABLE("Bad path segment type"); + return nullptr; // according to spec we'd use everything up to the bad + // seg anyway + } + + subpathContainsNonMoveTo = !IsMoveto(segType); + i += argCount; + prevSegType = segType; + segStart = segEnd; + } + + MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); + MOZ_ASSERT(prevSegType == segType, + "prevSegType should be left at the final segType"); + + MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; + + return aBuilder->Finish(); +} + +already_AddRefed SVGPathData::BuildPathForMeasuring() const { + // Since the path that we return will not be used for painting it doesn't + // matter what we pass to CreatePathBuilder as aFillRule. Hawever, we do want + // to pass something other than NS_STYLE_STROKE_LINECAP_SQUARE as + // aStrokeLineCap to avoid the insertion of extra little lines (by + // ApproximateZeroLengthSubpathSquareCaps), in which case the value that we + // pass as aStrokeWidth doesn't matter (since it's only used to determine the + // length of those extra little lines). + + RefPtr drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + RefPtr builder = + drawTarget->CreatePathBuilder(FillRule::FILL_WINDING); + return BuildPath(builder, StyleStrokeLinecap::Butt, 0); +} + +/* static */ +already_AddRefed SVGPathData::BuildPathForMeasuring( + Span aPath) { + RefPtr drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + RefPtr builder = + drawTarget->CreatePathBuilder(FillRule::FILL_WINDING); + return BuildPath(aPath, builder, StyleStrokeLinecap::Butt, 0); +} + +// We could simplify this function because this is only used by CSS motion path +// and clip-path, which don't render the SVG Path. i.e. The returned path is +// used as a reference. +/* static */ +already_AddRefed SVGPathData::BuildPath( + Span aPath, PathBuilder* aBuilder, + StyleStrokeLinecap aStrokeLineCap, Float aStrokeWidth, float aZoomFactor) { + if (aPath.IsEmpty() || !aPath[0].IsMoveTo()) { + return nullptr; // paths without an initial moveto are invalid + } + + bool hasLineCaps = aStrokeLineCap != StyleStrokeLinecap::Butt; + bool subpathHasLength = false; // visual length + bool subpathContainsNonMoveTo = false; + + StylePathCommand::Tag segType = StylePathCommand::Tag::Unknown; + StylePathCommand::Tag prevSegType = StylePathCommand::Tag::Unknown; + Point pathStart(0.0, 0.0); // start point of [sub]path + Point segStart(0.0, 0.0); + Point segEnd; + Point cp1, cp2; // previous bezier's control points + Point tcp1, tcp2; // temporaries + + auto scale = [aZoomFactor](const Point& p) { + return Point(p.x * aZoomFactor, p.y * aZoomFactor); + }; + + // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve, + // then cp2 is its second control point. If the previous segment was a + // quadratic curve, then cp1 is its (only) control point. + + for (const StylePathCommand& cmd : aPath) { + segType = cmd.tag; + switch (segType) { + case StylePathCommand::Tag::ClosePath: + // set this early to allow drawing of square caps for "M{x},{y} Z": + subpathContainsNonMoveTo = true; + MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; + segEnd = pathStart; + aBuilder->Close(); + break; + case StylePathCommand::Tag::MoveTo: { + MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; + const Point& p = cmd.move_to.point.ConvertsToGfxPoint(); + pathStart = segEnd = + cmd.move_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p; + aBuilder->MoveTo(scale(segEnd)); + subpathHasLength = false; + break; + } + case StylePathCommand::Tag::LineTo: { + const Point& p = cmd.line_to.point.ConvertsToGfxPoint(); + segEnd = + cmd.line_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p; + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(scale(segEnd)); + } + break; + } + case StylePathCommand::Tag::CurveTo: + cp1 = cmd.curve_to.control1.ConvertsToGfxPoint(); + cp2 = cmd.curve_to.control2.ConvertsToGfxPoint(); + segEnd = cmd.curve_to.point.ConvertsToGfxPoint(); + + if (cmd.curve_to.absolute == StyleIsAbsolute::No) { + cp1 += segStart; + cp2 += segStart; + segEnd += segStart; + } + + if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { + subpathHasLength = true; + aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd)); + } + break; + + case StylePathCommand::Tag::QuadBezierCurveTo: + cp1 = cmd.quad_bezier_curve_to.control1.ConvertsToGfxPoint(); + segEnd = cmd.quad_bezier_curve_to.point.ConvertsToGfxPoint(); + + if (cmd.quad_bezier_curve_to.absolute == StyleIsAbsolute::No) { + cp1 += segStart; + segEnd += segStart; // set before setting tcp2! + } + + // Convert quadratic curve to cubic curve: + tcp1 = segStart + (cp1 - segStart) * 2 / 3; + tcp2 = cp1 + (segEnd - cp1) / 3; + + if (segEnd != segStart || segEnd != cp1) { + subpathHasLength = true; + aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd)); + } + break; + + case StylePathCommand::Tag::EllipticalArc: { + const auto& arc = cmd.elliptical_arc; + Point radii(arc.rx, arc.ry); + segEnd = arc.point.ConvertsToGfxPoint(); + if (arc.absolute == StyleIsAbsolute::No) { + segEnd += segStart; + } + if (segEnd != segStart) { + subpathHasLength = true; + if (radii.x == 0.0f || radii.y == 0.0f) { + aBuilder->LineTo(scale(segEnd)); + } else { + SVGArcConverter converter(segStart, segEnd, radii, arc.angle, + arc.large_arc_flag._0, arc.sweep_flag._0); + while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) { + aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd)); + } + } + } + break; + } + case StylePathCommand::Tag::HorizontalLineTo: + if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::Yes) { + segEnd = Point(cmd.horizontal_line_to.x, segStart.y); + } else { + segEnd = segStart + Point(cmd.horizontal_line_to.x, 0.0f); + } + + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(scale(segEnd)); + } + break; + + case StylePathCommand::Tag::VerticalLineTo: + if (cmd.vertical_line_to.absolute == StyleIsAbsolute::Yes) { + segEnd = Point(segStart.x, cmd.vertical_line_to.y); + } else { + segEnd = segStart + Point(0.0f, cmd.vertical_line_to.y); + } + + if (segEnd != segStart) { + subpathHasLength = true; + aBuilder->LineTo(scale(segEnd)); + } + break; + + case StylePathCommand::Tag::SmoothCurveTo: + cp1 = IsCubicType(prevSegType) ? segStart * 2 - cp2 : segStart; + cp2 = cmd.smooth_curve_to.control2.ConvertsToGfxPoint(); + segEnd = cmd.smooth_curve_to.point.ConvertsToGfxPoint(); + + if (cmd.smooth_curve_to.absolute == StyleIsAbsolute::No) { + cp2 += segStart; + segEnd += segStart; + } + + if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { + subpathHasLength = true; + aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd)); + } + break; + + case StylePathCommand::Tag::SmoothQuadBezierCurveTo: { + cp1 = IsQuadraticType(prevSegType) ? segStart * 2 - cp1 : segStart; + // Convert quadratic curve to cubic curve: + tcp1 = segStart + (cp1 - segStart) * 2 / 3; + + const Point& p = + cmd.smooth_quad_bezier_curve_to.point.ConvertsToGfxPoint(); + // set before setting tcp2! + segEnd = + cmd.smooth_quad_bezier_curve_to.absolute == StyleIsAbsolute::Yes + ? p + : segStart + p; + tcp2 = cp1 + (segEnd - cp1) / 3; + + if (segEnd != segStart || segEnd != cp1) { + subpathHasLength = true; + aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd)); + } + break; + } + case StylePathCommand::Tag::Unknown: + MOZ_ASSERT_UNREACHABLE("Unacceptable path segment type"); + return nullptr; + } + + subpathContainsNonMoveTo = !IsMoveto(segType); + prevSegType = segType; + segStart = segEnd; + } + + MOZ_ASSERT(prevSegType == segType, + "prevSegType should be left at the final segType"); + + MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT; + + return aBuilder->Finish(); +} + +static double AngleOfVector(const Point& aVector) { + // C99 says about atan2 "A domain error may occur if both arguments are + // zero" and "On a domain error, the function returns an implementation- + // defined value". In the case of atan2 the implementation-defined value + // seems to commonly be zero, but it could just as easily be a NaN value. + // We specifically want zero in this case, hence the check: + + return (aVector != Point(0.0, 0.0)) ? atan2(aVector.y, aVector.x) : 0.0; +} + +static float AngleOfVector(const Point& cp1, const Point& cp2) { + return static_cast(AngleOfVector(cp1 - cp2)); +} + +// This implements F.6.5 and F.6.6 of +// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes +static std::tuple +/* rx, ry, segStartAngle, segEndAngle */ +ComputeSegAnglesAndCorrectRadii(const Point& aSegStart, const Point& aSegEnd, + const float aAngle, const bool aLargeArcFlag, + const bool aSweepFlag, const float aRx, + const float aRy) { + float rx = fabs(aRx); // F.6.6.1 + float ry = fabs(aRy); + + // F.6.5.1: + const float angle = static_cast(aAngle * M_PI / 180.0); + double x1p = cos(angle) * (aSegStart.x - aSegEnd.x) / 2.0 + + sin(angle) * (aSegStart.y - aSegEnd.y) / 2.0; + double y1p = -sin(angle) * (aSegStart.x - aSegEnd.x) / 2.0 + + cos(angle) * (aSegStart.y - aSegEnd.y) / 2.0; + + // This is the root in F.6.5.2 and the numerator under that root: + double root; + double numerator = + rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p; + + if (numerator >= 0.0) { + root = sqrt(numerator / (rx * rx * y1p * y1p + ry * ry * x1p * x1p)); + if (aLargeArcFlag == aSweepFlag) root = -root; + } else { + // F.6.6 step 3 - |numerator < 0.0|. This is equivalent to the result + // of F.6.6.2 (lamedh) being greater than one. What we have here is + // ellipse radii that are too small for the ellipse to reach between + // segStart and segEnd. We scale the radii up uniformly so that the + // ellipse is just big enough to fit (i.e. to the point where there is + // exactly one solution). + + double lamedh = + 1.0 - numerator / (rx * rx * ry * ry); // equiv to eqn F.6.6.2 + double s = sqrt(lamedh); + rx = static_cast((double)rx * s); // F.6.6.3 + ry = static_cast((double)ry * s); + root = 0.0; + } + + double cxp = root * rx * y1p / ry; // F.6.5.2 + double cyp = -root * ry * x1p / rx; + + double theta = + AngleOfVector(Point(static_cast((x1p - cxp) / rx), + static_cast((y1p - cyp) / ry))); // F.6.5.5 + double delta = + AngleOfVector(Point(static_cast((-x1p - cxp) / rx), + static_cast((-y1p - cyp) / ry))) - // F.6.5.6 + theta; + if (!aSweepFlag && delta > 0) { + delta -= 2.0 * M_PI; + } else if (aSweepFlag && delta < 0) { + delta += 2.0 * M_PI; + } + + double tx1, ty1, tx2, ty2; + tx1 = -cos(angle) * rx * sin(theta) - sin(angle) * ry * cos(theta); + ty1 = -sin(angle) * rx * sin(theta) + cos(angle) * ry * cos(theta); + tx2 = -cos(angle) * rx * sin(theta + delta) - + sin(angle) * ry * cos(theta + delta); + ty2 = -sin(angle) * rx * sin(theta + delta) + + cos(angle) * ry * cos(theta + delta); + + if (delta < 0.0f) { + tx1 = -tx1; + ty1 = -ty1; + tx2 = -tx2; + ty2 = -ty2; + } + + return {rx, ry, static_cast(atan2(ty1, tx1)), + static_cast(atan2(ty2, tx2))}; +} + +void SVGPathData::GetMarkerPositioningData(nsTArray* aMarks) const { + // This code should assume that ANY type of segment can appear at ANY index. + // It should also assume that segments such as M and Z can appear in weird + // places, and repeat multiple times consecutively. + + // info on current [sub]path (reset every M command): + Point pathStart(0.0, 0.0); + float pathStartAngle = 0.0f; + uint32_t pathStartIndex = 0; + + // info on previous segment: + uint16_t prevSegType = PATHSEG_UNKNOWN; + Point prevSegEnd(0.0, 0.0); + float prevSegEndAngle = 0.0f; + Point prevCP; // if prev seg was a bezier, this was its last control point + + uint32_t i = 0; + while (i < mData.Length()) { + // info on current segment: + uint16_t segType = + SVGPathSegUtils::DecodeType(mData[i++]); // advances i to args + Point& segStart = prevSegEnd; + Point segEnd; + float segStartAngle, segEndAngle; + + switch (segType) // to find segStartAngle, segEnd and segEndAngle + { + case PATHSEG_CLOSEPATH: + segEnd = pathStart; + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + + case PATHSEG_MOVETO_ABS: + case PATHSEG_MOVETO_REL: + if (segType == PATHSEG_MOVETO_ABS) { + segEnd = Point(mData[i], mData[i + 1]); + } else { + segEnd = segStart + Point(mData[i], mData[i + 1]); + } + pathStart = segEnd; + pathStartIndex = aMarks->Length(); + // If authors are going to specify multiple consecutive moveto commands + // with markers, me might as well make the angle do something useful: + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + i += 2; + break; + + case PATHSEG_LINETO_ABS: + case PATHSEG_LINETO_REL: + if (segType == PATHSEG_LINETO_ABS) { + segEnd = Point(mData[i], mData[i + 1]); + } else { + segEnd = segStart + Point(mData[i], mData[i + 1]); + } + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + i += 2; + break; + + case PATHSEG_CURVETO_CUBIC_ABS: + case PATHSEG_CURVETO_CUBIC_REL: { + Point cp1, cp2; // control points + if (segType == PATHSEG_CURVETO_CUBIC_ABS) { + cp1 = Point(mData[i], mData[i + 1]); + cp2 = Point(mData[i + 2], mData[i + 3]); + segEnd = Point(mData[i + 4], mData[i + 5]); + } else { + cp1 = segStart + Point(mData[i], mData[i + 1]); + cp2 = segStart + Point(mData[i + 2], mData[i + 3]); + segEnd = segStart + Point(mData[i + 4], mData[i + 5]); + } + prevCP = cp2; + segStartAngle = AngleOfVector( + cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart); + segEndAngle = AngleOfVector( + segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2); + i += 6; + break; + } + + case PATHSEG_CURVETO_QUADRATIC_ABS: + case PATHSEG_CURVETO_QUADRATIC_REL: { + Point cp1; // control point + if (segType == PATHSEG_CURVETO_QUADRATIC_ABS) { + cp1 = Point(mData[i], mData[i + 1]); + segEnd = Point(mData[i + 2], mData[i + 3]); + } else { + cp1 = segStart + Point(mData[i], mData[i + 1]); + segEnd = segStart + Point(mData[i + 2], mData[i + 3]); + } + prevCP = cp1; + segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart); + segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1); + i += 4; + break; + } + + case PATHSEG_ARC_ABS: + case PATHSEG_ARC_REL: { + float rx = mData[i]; + float ry = mData[i + 1]; + float angle = mData[i + 2]; + bool largeArcFlag = mData[i + 3] != 0.0f; + bool sweepFlag = mData[i + 4] != 0.0f; + if (segType == PATHSEG_ARC_ABS) { + segEnd = Point(mData[i + 5], mData[i + 6]); + } else { + segEnd = segStart + Point(mData[i + 5], mData[i + 6]); + } + + // See section F.6 of SVG 1.1 for details on what we're doing here: + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + + if (segStart == segEnd) { + // F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical, + // then this is equivalent to omitting the elliptical arc segment + // entirely." We take that very literally here, not adding a mark, and + // not even setting any of the 'prev' variables so that it's as if + // this arc had never existed; note the difference this will make e.g. + // if the arc is proceeded by a bezier curve and followed by a + // "smooth" bezier curve of the same degree! + i += 7; + continue; + } + + // Below we have funny interleaving of F.6.6 (Correction of out-of-range + // radii) and F.6.5 (Conversion from endpoint to center + // parameterization) which is designed to avoid some unnecessary + // calculations. + + if (rx == 0.0 || ry == 0.0) { + // F.6.6 step 1 - straight line or coincidental points + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + i += 7; + break; + } + + std::tie(rx, ry, segStartAngle, segEndAngle) = + ComputeSegAnglesAndCorrectRadii(segStart, segEnd, angle, + largeArcFlag, sweepFlag, rx, ry); + i += 7; + break; + } + + case PATHSEG_LINETO_HORIZONTAL_ABS: + case PATHSEG_LINETO_HORIZONTAL_REL: + if (segType == PATHSEG_LINETO_HORIZONTAL_ABS) { + segEnd = Point(mData[i++], segStart.y); + } else { + segEnd = segStart + Point(mData[i++], 0.0f); + } + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + + case PATHSEG_LINETO_VERTICAL_ABS: + case PATHSEG_LINETO_VERTICAL_REL: + if (segType == PATHSEG_LINETO_VERTICAL_ABS) { + segEnd = Point(segStart.x, mData[i++]); + } else { + segEnd = segStart + Point(0.0f, mData[i++]); + } + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + + case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS: + case PATHSEG_CURVETO_CUBIC_SMOOTH_REL: { + Point cp1 = SVGPathSegUtils::IsCubicType(prevSegType) + ? segStart * 2 - prevCP + : segStart; + Point cp2; + if (segType == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS) { + cp2 = Point(mData[i], mData[i + 1]); + segEnd = Point(mData[i + 2], mData[i + 3]); + } else { + cp2 = segStart + Point(mData[i], mData[i + 1]); + segEnd = segStart + Point(mData[i + 2], mData[i + 3]); + } + prevCP = cp2; + segStartAngle = AngleOfVector( + cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart); + segEndAngle = AngleOfVector( + segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2); + i += 4; + break; + } + + case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS: + case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL: { + Point cp1 = SVGPathSegUtils::IsQuadraticType(prevSegType) + ? segStart * 2 - prevCP + : segStart; + if (segType == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS) { + segEnd = Point(mData[i], mData[i + 1]); + } else { + segEnd = segStart + Point(mData[i], mData[i + 1]); + } + prevCP = cp1; + segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart); + segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1); + i += 2; + break; + } + + default: + // Leave any existing marks in aMarks so we have a visual indication of + // when things went wrong. + MOZ_ASSERT(false, "Unknown segment type - path corruption?"); + return; + } + + // Set the angle of the mark at the start of this segment: + if (aMarks->Length()) { + SVGMark& mark = aMarks->LastElement(); + if (!IsMoveto(segType) && IsMoveto(prevSegType)) { + // start of new subpath + pathStartAngle = mark.angle = segStartAngle; + } else if (IsMoveto(segType) && !IsMoveto(prevSegType)) { + // end of a subpath + if (prevSegType != PATHSEG_CLOSEPATH) mark.angle = prevSegEndAngle; + } else { + if (!(segType == PATHSEG_CLOSEPATH && prevSegType == PATHSEG_CLOSEPATH)) + mark.angle = + SVGContentUtils::AngleBisect(prevSegEndAngle, segStartAngle); + } + } + + // Add the mark at the end of this segment, and set its position: + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + aMarks->AppendElement(SVGMark(static_cast(segEnd.x), + static_cast(segEnd.y), 0.0f, + SVGMark::eMid)); + + if (segType == PATHSEG_CLOSEPATH && prevSegType != PATHSEG_CLOSEPATH) { + aMarks->LastElement().angle = aMarks->ElementAt(pathStartIndex).angle = + SVGContentUtils::AngleBisect(segEndAngle, pathStartAngle); + } + + prevSegType = segType; + prevSegEnd = segEnd; + prevSegEndAngle = segEndAngle; + } + + MOZ_ASSERT(i == mData.Length(), "Very, very bad - mData corrupt"); + + if (aMarks->Length()) { + if (prevSegType != PATHSEG_CLOSEPATH) { + aMarks->LastElement().angle = prevSegEndAngle; + } + aMarks->LastElement().type = SVGMark::eEnd; + aMarks->ElementAt(0).type = SVGMark::eStart; + } +} + +// Basically, this is identical to the above function, but replace |mData| with +// |aPath|. We probably can factor out some identical calculation, but I believe +// the above one will be removed because we will use any kind of array of +// StylePathCommand for SVG d attribute in the future. +/* static */ +void SVGPathData::GetMarkerPositioningData(Span aPath, + nsTArray* aMarks) { + if (aPath.IsEmpty()) { + return; + } + + // info on current [sub]path (reset every M command): + Point pathStart(0.0, 0.0); + float pathStartAngle = 0.0f; + uint32_t pathStartIndex = 0; + + // info on previous segment: + StylePathCommand::Tag prevSegType = StylePathCommand::Tag::Unknown; + Point prevSegEnd(0.0, 0.0); + float prevSegEndAngle = 0.0f; + Point prevCP; // if prev seg was a bezier, this was its last control point + + StylePathCommand::Tag segType = StylePathCommand::Tag::Unknown; + for (const StylePathCommand& cmd : aPath) { + segType = cmd.tag; + Point& segStart = prevSegEnd; + Point segEnd; + float segStartAngle, segEndAngle; + + switch (segType) // to find segStartAngle, segEnd and segEndAngle + { + case StylePathCommand::Tag::ClosePath: + segEnd = pathStart; + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + + case StylePathCommand::Tag::MoveTo: { + const Point& p = cmd.move_to.point.ConvertsToGfxPoint(); + pathStart = segEnd = + cmd.move_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p; + pathStartIndex = aMarks->Length(); + // If authors are going to specify multiple consecutive moveto commands + // with markers, me might as well make the angle do something useful: + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + } + case StylePathCommand::Tag::LineTo: { + const Point& p = cmd.line_to.point.ConvertsToGfxPoint(); + segEnd = + cmd.line_to.absolute == StyleIsAbsolute::Yes ? p : segStart + p; + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + } + case StylePathCommand::Tag::CurveTo: { + Point cp1 = cmd.curve_to.control1.ConvertsToGfxPoint(); + Point cp2 = cmd.curve_to.control2.ConvertsToGfxPoint(); + segEnd = cmd.curve_to.point.ConvertsToGfxPoint(); + + if (cmd.curve_to.absolute == StyleIsAbsolute::No) { + cp1 += segStart; + cp2 += segStart; + segEnd += segStart; + } + + prevCP = cp2; + segStartAngle = AngleOfVector( + cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart); + segEndAngle = AngleOfVector( + segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2); + break; + } + case StylePathCommand::Tag::QuadBezierCurveTo: { + Point cp1 = cmd.quad_bezier_curve_to.control1.ConvertsToGfxPoint(); + segEnd = cmd.quad_bezier_curve_to.point.ConvertsToGfxPoint(); + + if (cmd.quad_bezier_curve_to.absolute == StyleIsAbsolute::No) { + cp1 += segStart; + segEnd += segStart; // set before setting tcp2! + } + + prevCP = cp1; + segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart); + segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1); + break; + } + case StylePathCommand::Tag::EllipticalArc: { + const auto& arc = cmd.elliptical_arc; + float rx = arc.rx; + float ry = arc.ry; + float angle = arc.angle; + bool largeArcFlag = arc.large_arc_flag._0; + bool sweepFlag = arc.sweep_flag._0; + Point radii(arc.rx, arc.ry); + segEnd = arc.point.ConvertsToGfxPoint(); + if (arc.absolute == StyleIsAbsolute::No) { + segEnd += segStart; + } + + // See section F.6 of SVG 1.1 for details on what we're doing here: + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + + if (segStart == segEnd) { + // F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical, + // then this is equivalent to omitting the elliptical arc segment + // entirely." We take that very literally here, not adding a mark, and + // not even setting any of the 'prev' variables so that it's as if + // this arc had never existed; note the difference this will make e.g. + // if the arc is proceeded by a bezier curve and followed by a + // "smooth" bezier curve of the same degree! + continue; + } + + // Below we have funny interleaving of F.6.6 (Correction of out-of-range + // radii) and F.6.5 (Conversion from endpoint to center + // parameterization) which is designed to avoid some unnecessary + // calculations. + + if (rx == 0.0 || ry == 0.0) { + // F.6.6 step 1 - straight line or coincidental points + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + } + + std::tie(rx, ry, segStartAngle, segEndAngle) = + ComputeSegAnglesAndCorrectRadii(segStart, segEnd, angle, + largeArcFlag, sweepFlag, rx, ry); + break; + } + case StylePathCommand::Tag::HorizontalLineTo: { + if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::Yes) { + segEnd = Point(cmd.horizontal_line_to.x, segStart.y); + } else { + segEnd = segStart + Point(cmd.horizontal_line_to.x, 0.0f); + } + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + } + case StylePathCommand::Tag::VerticalLineTo: { + if (cmd.vertical_line_to.absolute == StyleIsAbsolute::Yes) { + segEnd = Point(segStart.x, cmd.vertical_line_to.y); + } else { + segEnd = segStart + Point(0.0f, cmd.vertical_line_to.y); + } + segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); + break; + } + case StylePathCommand::Tag::SmoothCurveTo: { + Point cp1 = IsCubicType(prevSegType) ? segStart * 2 - prevCP : segStart; + Point cp2 = cmd.smooth_curve_to.control2.ConvertsToGfxPoint(); + segEnd = cmd.smooth_curve_to.point.ConvertsToGfxPoint(); + + if (cmd.smooth_curve_to.absolute == StyleIsAbsolute::No) { + cp2 += segStart; + segEnd += segStart; + } + + prevCP = cp2; + segStartAngle = AngleOfVector( + cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart); + segEndAngle = AngleOfVector( + segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2); + break; + } + case StylePathCommand::Tag::SmoothQuadBezierCurveTo: { + Point cp1 = + IsQuadraticType(prevSegType) ? segStart * 2 - prevCP : segStart; + segEnd = + cmd.smooth_quad_bezier_curve_to.absolute == StyleIsAbsolute::Yes + ? cmd.smooth_quad_bezier_curve_to.point.ConvertsToGfxPoint() + : segStart + cmd.smooth_quad_bezier_curve_to.point + .ConvertsToGfxPoint(); + + prevCP = cp1; + segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart); + segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1); + break; + } + case StylePathCommand::Tag::Unknown: + // Leave any existing marks in aMarks so we have a visual indication of + // when things went wrong. + MOZ_ASSERT_UNREACHABLE("Unknown segment type - path corruption?"); + return; + } + + // Set the angle of the mark at the start of this segment: + if (aMarks->Length()) { + SVGMark& mark = aMarks->LastElement(); + if (!IsMoveto(segType) && IsMoveto(prevSegType)) { + // start of new subpath + pathStartAngle = mark.angle = segStartAngle; + } else if (IsMoveto(segType) && !IsMoveto(prevSegType)) { + // end of a subpath + if (prevSegType != StylePathCommand::Tag::ClosePath) { + mark.angle = prevSegEndAngle; + } + } else if (!(segType == StylePathCommand::Tag::ClosePath && + prevSegType == StylePathCommand::Tag::ClosePath)) { + mark.angle = + SVGContentUtils::AngleBisect(prevSegEndAngle, segStartAngle); + } + } + + // Add the mark at the end of this segment, and set its position: + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + aMarks->AppendElement(SVGMark(static_cast(segEnd.x), + static_cast(segEnd.y), 0.0f, + SVGMark::eMid)); + + if (segType == StylePathCommand::Tag::ClosePath && + prevSegType != StylePathCommand::Tag::ClosePath) { + aMarks->LastElement().angle = aMarks->ElementAt(pathStartIndex).angle = + SVGContentUtils::AngleBisect(segEndAngle, pathStartAngle); + } + + prevSegType = segType; + prevSegEnd = segEnd; + prevSegEndAngle = segEndAngle; + } + + if (aMarks->Length()) { + if (prevSegType != StylePathCommand::Tag::ClosePath) { + aMarks->LastElement().angle = prevSegEndAngle; + } + aMarks->LastElement().type = SVGMark::eEnd; + aMarks->ElementAt(0).type = SVGMark::eStart; + } +} + +size_t SVGPathData::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return mData.ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +size_t SVGPathData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +} // namespace mozilla diff --git a/dom/svg/SVGPathData.h b/dom/svg/SVGPathData.h new file mode 100644 index 0000000000..70768af9ae --- /dev/null +++ b/dom/svg/SVGPathData.h @@ -0,0 +1,312 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPATHDATA_H_ +#define DOM_SVG_SVGPATHDATA_H_ + +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsIContent.h" +#include "nsINode.h" +#include "nsIWeakReferenceUtils.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "nsTArray.h" + +#include + +namespace mozilla { + +struct StylePathCommand; +struct SVGMark; +enum class StyleStrokeLinecap : uint8_t; + +class SVGPathDataParser; // IWYU pragma: keep + +namespace dom { +class DOMSVGPathSeg; +class DOMSVGPathSegList; +} // namespace dom + +/** + * ATTENTION! WARNING! WATCH OUT!! + * + * Consumers that modify objects of this type absolutely MUST keep the DOM + * wrappers for those lists (if any) in sync!! That's why this class is so + * locked down. + * + * The DOM wrapper class for this class is DOMSVGPathSegList. + * + * This class is not called |class SVGPathSegList| for one very good reason; + * this class does not provide a list of "SVGPathSeg" items, it provides an + * array of floats into which path segments are encoded. See the paragraphs + * that follow for why. Note that the Length() method returns the number of + * floats in our array, not the number of encoded segments, and the index + * operator indexes floats in the array, not segments. If this class were + * called SVGPathSegList the names of these methods would be very misleading. + * + * The reason this class is designed in this way is because there are many + * different types of path segment, each taking a different numbers of + * arguments. We want to store the segments in an nsTArray to avoid individual + * allocations for each item, but the different size of segments means we can't + * have one single segment type for the nsTArray (not without using a space + * wasteful union or something similar). Since the internal code does not need + * to index into the list (the DOM wrapper does, but it handles that itself) + * the obvious solution is to have the items in this class take up variable + * width and have the internal code iterate over these lists rather than index + * into them. + * + * Implementing indexing to segments with O(1) performance would require us to + * allocate and maintain a separate segment index table (keeping that table in + * sync when items are inserted or removed from the list). So long as the + * internal code doesn't require indexing to segments, we can avoid that + * overhead and additional complexity. + * + * Segment encoding: the first float in the encoding of a segment contains the + * segment's type. The segment's type is encoded to/decoded from this float + * using the static methods SVGPathSegUtils::EncodeType(uint32_t)/ + * SVGPathSegUtils::DecodeType(float). If the path segment type in question + * takes any arguments then these follow the first float, and are in the same + * order as they are given in a element's 'd' attribute (NOT in the + * order of the createSVGPathSegXxx() methods' arguments from the SVG DOM + * interface SVGPathElement, which are different...grr). Consumers can use + * SVGPathSegUtils::ArgCountForType(type) to determine how many arguments + * there are (if any), and thus where the current encoded segment ends, and + * where the next segment (if any) begins. + */ +class SVGPathData { + friend class SVGAnimatedPathSegList; + friend class dom::DOMSVGPathSeg; + friend class dom::DOMSVGPathSegList; + friend class SVGPathDataParser; + // SVGPathDataParser will not keep wrappers in sync, so consumers + // are responsible for that! + + using DrawTarget = gfx::DrawTarget; + using Path = gfx::Path; + using PathBuilder = gfx::PathBuilder; + using FillRule = gfx::FillRule; + using Float = gfx::Float; + using CapStyle = gfx::CapStyle; + + public: + using const_iterator = const float*; + + SVGPathData() = default; + ~SVGPathData() = default; + + // Only methods that don't make/permit modification to this list are public. + // Only our friend classes can access methods that may change us. + + /// This may return an incomplete string on OOM, but that's acceptable. + void GetValueAsString(nsAString& aValue) const; + + bool IsEmpty() const { return mData.IsEmpty(); } + +#ifdef DEBUG + /** + * This method iterates over the encoded segment data and counts the number + * of segments we currently have. + */ + uint32_t CountItems() const; +#endif + + /** + * Returns the number of *floats* in the encoding array, and NOT the number + * of segments encoded in this object. (For that, see CountItems() above.) + */ + uint32_t Length() const { return mData.Length(); } + + const nsTArray& RawData() const { return mData; } + + const float& operator[](uint32_t aIndex) const { return mData[aIndex]; } + + // Used by SMILCompositor to check if the cached base val is out of date + bool operator==(const SVGPathData& rhs) const { + // We use memcmp so that we don't need to worry that the data encoded in + // the first float may have the same bit pattern as a NaN. + return mData.Length() == rhs.mData.Length() && + memcmp(mData.Elements(), rhs.mData.Elements(), + mData.Length() * sizeof(float)) == 0; + } + + bool SetCapacity(uint32_t aSize) { + return mData.SetCapacity(aSize, fallible); + } + + void Compact() { mData.Compact(); } + + float GetPathLength() const; + + uint32_t GetPathSegAtLength(float aDistance) const; + + static uint32_t GetPathSegAtLength(Span aPath, + float aDistance); + + void GetMarkerPositioningData(nsTArray* aMarks) const; + + static void GetMarkerPositioningData(Span aPath, + nsTArray* aMarks); + + /** + * Returns true, except on OOM, in which case returns false. + */ + bool GetDistancesFromOriginToEndsOfVisibleSegments( + FallibleTArray* aOutput) const; + + /** + * This is identical to the above one but accepts StylePathCommand. + */ + static bool GetDistancesFromOriginToEndsOfVisibleSegments( + Span aPath, FallibleTArray* aOutput); + + /** + * This returns a path without the extra little line segments that + * ApproximateZeroLengthSubpathSquareCaps can insert if we have square-caps. + * See the comment for that function for more info on that. + */ + already_AddRefed BuildPathForMeasuring() const; + + already_AddRefed BuildPath(PathBuilder* aBuilder, + StyleStrokeLinecap aStrokeLineCap, + Float aStrokeWidth) const; + + static already_AddRefed BuildPathForMeasuring( + Span aPath); + + /** + * This function tries to build the path from an array of StylePathCommand, + * which is generated by cbindgen from Rust (see ServoStyleConsts.h). + * Basically, this is a variant of the above BuildPath() functions. + */ + static already_AddRefed BuildPath(Span aPath, + PathBuilder* aBuilder, + StyleStrokeLinecap aStrokeLineCap, + Float aStrokeWidth, + float aZoomFactor = 1.0); + + const_iterator begin() const { return mData.Elements(); } + const_iterator end() const { return mData.Elements() + mData.Length(); } + + // memory reporting methods + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + // Access to methods that can modify objects of this type is deliberately + // limited. This is to reduce the chances of someone modifying objects of + // this type without taking the necessary steps to keep DOM wrappers in sync. + // If you need wider access to these methods, consider adding a method to + // SVGAnimatedPathSegList and having that class act as an intermediary so it + // can take care of keeping DOM wrappers in sync. + + protected: + using iterator = float*; + + /** + * This may fail on OOM if the internal capacity needs to be increased, in + * which case the list will be left unmodified. + */ + nsresult CopyFrom(const SVGPathData& rhs); + void SwapWith(SVGPathData& aRhs) { mData.SwapElements(aRhs.mData); } + + float& operator[](uint32_t aIndex) { return mData[aIndex]; } + + /** + * This may fail (return false) on OOM if the internal capacity is being + * increased, in which case the list will be left unmodified. + */ + bool SetLength(uint32_t aLength) { + return mData.SetLength(aLength, fallible); + } + + nsresult SetValueFromString(const nsAString& aValue); + + void Clear() { mData.Clear(); } + + // Our DOM wrappers have direct access to our mData, so they directly + // manipulate it rather than us implementing: + // + // * InsertItem(uint32_t aDataIndex, uint32_t aType, const float *aArgs); + // * ReplaceItem(uint32_t aDataIndex, uint32_t aType, const float *aArgs); + // * RemoveItem(uint32_t aDataIndex); + // * bool AppendItem(uint32_t aType, const float *aArgs); + + nsresult AppendSeg(uint32_t aType, ...); // variable number of float args + + iterator begin() { return mData.Elements(); } + iterator end() { return mData.Elements() + mData.Length(); } + + FallibleTArray mData; +}; + +/** + * This SVGPathData subclass is for SVGPathSegListSMILType which needs to + * have write access to the lists it works with. + * + * Instances of this class do not have DOM wrappers that need to be kept in + * sync, so we can safely expose any protected base class methods required by + * the SMIL code. + */ +class SVGPathDataAndInfo final : public SVGPathData { + public: + explicit SVGPathDataAndInfo(dom::SVGElement* aElement = nullptr) + : mElement(do_GetWeakReference(static_cast(aElement))) {} + + void SetElement(dom::SVGElement* aElement) { + mElement = do_GetWeakReference(static_cast(aElement)); + } + + dom::SVGElement* Element() const { + nsCOMPtr e = do_QueryReferent(mElement); + return static_cast(e.get()); + } + + nsresult CopyFrom(const SVGPathDataAndInfo& rhs) { + mElement = rhs.mElement; + return SVGPathData::CopyFrom(rhs); + } + + /** + * Returns true if this object is an "identity" value, from the perspective + * of SMIL. In other words, returns true until the initial value set up in + * SVGPathSegListSMILType::Init() has been changed with a SetElement() call. + */ + bool IsIdentity() const { + if (!mElement) { + MOZ_ASSERT(IsEmpty(), "target element propagation failure"); + return true; + } + return false; + } + + /** + * Exposed so that SVGPathData baseVals can be copied to + * SVGPathDataAndInfo objects. Note that callers should also call + * SetElement() when using this method! + */ + using SVGPathData::CopyFrom; + + // Exposed since SVGPathData objects can be modified. + using SVGPathData::iterator; + using SVGPathData::operator[]; + using SVGPathData::begin; + using SVGPathData::end; + using SVGPathData::SetLength; + + private: + // We must keep a weak reference to our element because we may belong to a + // cached baseVal SMILValue. See the comments starting at: + // https://bugzilla.mozilla.org/show_bug.cgi?id=515116#c15 + // See also https://bugzilla.mozilla.org/show_bug.cgi?id=653497 + nsWeakPtr mElement; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGPATHDATA_H_ diff --git a/dom/svg/SVGPathDataParser.cpp b/dom/svg/SVGPathDataParser.cpp new file mode 100644 index 0000000000..b08b98c98f --- /dev/null +++ b/dom/svg/SVGPathDataParser.cpp @@ -0,0 +1,464 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGPathDataParser.h" + +#include "mozilla/gfx/Point.h" +#include "SVGDataParser.h" +#include "SVGContentUtils.h" +#include "SVGPathData.h" +#include "SVGPathSegUtils.h" + +using namespace mozilla::dom::SVGPathSeg_Binding; +using namespace mozilla::gfx; + +namespace mozilla { + +static inline char16_t ToUpper(char16_t aCh) { + return aCh >= 'a' && aCh <= 'z' ? aCh - 'a' + 'A' : aCh; +} + +bool SVGPathDataParser::Parse() { + mPathSegList->Clear(); + return ParsePath(); +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseCoordPair(float& aX, float& aY) { + return SVGContentUtils::ParseNumber(mIter, mEnd, aX) && SkipCommaWsp() && + SVGContentUtils::ParseNumber(mIter, mEnd, aY); +} + +bool SVGPathDataParser::ParseFlag(bool& aFlag) { + if (mIter == mEnd || (*mIter != '0' && *mIter != '1')) { + return false; + } + aFlag = (*mIter == '1'); + + ++mIter; + return true; +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParsePath() { + while (SkipWsp()) { + if (!ParseSubPath()) { + return false; + } + } + + return true; +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseSubPath() { + return ParseMoveto() && ParseSubPathElements(); +} + +bool SVGPathDataParser::ParseSubPathElements() { + while (SkipWsp() && !IsStartOfSubPath()) { + char16_t commandType = ToUpper(*mIter); + + // Upper case commands have absolute co-ordinates, + // lower case commands have relative co-ordinates. + bool absCoords = commandType == *mIter; + + ++mIter; + SkipWsp(); + + if (!ParseSubPathElement(commandType, absCoords)) { + return false; + } + } + return true; +} + +bool SVGPathDataParser::ParseSubPathElement(char16_t aCommandType, + bool aAbsCoords) { + switch (aCommandType) { + case 'Z': + return ParseClosePath(); + case 'L': + return ParseLineto(aAbsCoords); + case 'H': + return ParseHorizontalLineto(aAbsCoords); + case 'V': + return ParseVerticalLineto(aAbsCoords); + case 'C': + return ParseCurveto(aAbsCoords); + case 'S': + return ParseSmoothCurveto(aAbsCoords); + case 'Q': + return ParseQuadBezierCurveto(aAbsCoords); + case 'T': + return ParseSmoothQuadBezierCurveto(aAbsCoords); + case 'A': + return ParseEllipticalArc(aAbsCoords); + } + return false; +} + +bool SVGPathDataParser::IsStartOfSubPath() const { + return *mIter == 'm' || *mIter == 'M'; +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseMoveto() { + if (!IsStartOfSubPath()) { + return false; + } + + bool absCoords = (*mIter == 'M'); + + ++mIter; + SkipWsp(); + + float x, y; + if (!ParseCoordPair(x, y)) { + return false; + } + + if (NS_FAILED(mPathSegList->AppendSeg( + absCoords ? PATHSEG_MOVETO_ABS : PATHSEG_MOVETO_REL, x, y))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // End of data, or start of a new command + return true; + } + + SkipCommaWsp(); + + // Per SVG 1.1 Section 8.3.2 + // If a moveto is followed by multiple pairs of coordinates, + // the subsequent pairs are treated as implicit lineto commands + return ParseLineto(absCoords); +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseClosePath() { + return NS_SUCCEEDED(mPathSegList->AppendSeg(PATHSEG_CLOSEPATH)); +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseLineto(bool aAbsCoords) { + while (true) { + float x, y; + if (!ParseCoordPair(x, y)) { + return false; + } + + if (NS_FAILED(mPathSegList->AppendSeg( + aAbsCoords ? PATHSEG_LINETO_ABS : PATHSEG_LINETO_REL, x, y))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // End of data, or start of a new command + return true; + } + SkipCommaWsp(); + } +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseHorizontalLineto(bool aAbsCoords) { + while (true) { + float x; + if (!SVGContentUtils::ParseNumber(mIter, mEnd, x)) { + return false; + } + + if (NS_FAILED(mPathSegList->AppendSeg(aAbsCoords + ? PATHSEG_LINETO_HORIZONTAL_ABS + : PATHSEG_LINETO_HORIZONTAL_REL, + x))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // End of data, or start of a new command + return true; + } + SkipCommaWsp(); + } +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseVerticalLineto(bool aAbsCoords) { + while (true) { + float y; + if (!SVGContentUtils::ParseNumber(mIter, mEnd, y)) { + return false; + } + + if (NS_FAILED(mPathSegList->AppendSeg(aAbsCoords + ? PATHSEG_LINETO_VERTICAL_ABS + : PATHSEG_LINETO_VERTICAL_REL, + y))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // End of data, or start of a new command + return true; + } + SkipCommaWsp(); + } +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseCurveto(bool aAbsCoords) { + while (true) { + float x1, y1, x2, y2, x, y; + + if (!(ParseCoordPair(x1, y1) && SkipCommaWsp() && ParseCoordPair(x2, y2) && + SkipCommaWsp() && ParseCoordPair(x, y))) { + return false; + } + + if (NS_FAILED(mPathSegList->AppendSeg( + aAbsCoords ? PATHSEG_CURVETO_CUBIC_ABS : PATHSEG_CURVETO_CUBIC_REL, + x1, y1, x2, y2, x, y))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // End of data, or start of a new command + return true; + } + SkipCommaWsp(); + } +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseSmoothCurveto(bool aAbsCoords) { + while (true) { + float x2, y2, x, y; + if (!(ParseCoordPair(x2, y2) && SkipCommaWsp() && ParseCoordPair(x, y))) { + return false; + } + + if (NS_FAILED(mPathSegList->AppendSeg( + aAbsCoords ? PATHSEG_CURVETO_CUBIC_SMOOTH_ABS + : PATHSEG_CURVETO_CUBIC_SMOOTH_REL, + x2, y2, x, y))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // End of data, or start of a new command + return true; + } + SkipCommaWsp(); + } +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseQuadBezierCurveto(bool aAbsCoords) { + while (true) { + float x1, y1, x, y; + if (!(ParseCoordPair(x1, y1) && SkipCommaWsp() && ParseCoordPair(x, y))) { + return false; + } + + if (NS_FAILED(mPathSegList->AppendSeg(aAbsCoords + ? PATHSEG_CURVETO_QUADRATIC_ABS + : PATHSEG_CURVETO_QUADRATIC_REL, + x1, y1, x, y))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // Start of a new command + return true; + } + SkipCommaWsp(); + } +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseSmoothQuadBezierCurveto(bool aAbsCoords) { + while (true) { + float x, y; + if (!ParseCoordPair(x, y)) { + return false; + } + + if (NS_FAILED(mPathSegList->AppendSeg( + aAbsCoords ? PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS + : PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, + x, y))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // End of data, or start of a new command + return true; + } + SkipCommaWsp(); + } +} + +//---------------------------------------------------------------------- + +bool SVGPathDataParser::ParseEllipticalArc(bool aAbsCoords) { + while (true) { + float r1, r2, angle, x, y; + bool largeArcFlag, sweepFlag; + + if (!(SVGContentUtils::ParseNumber(mIter, mEnd, r1) && SkipCommaWsp() && + SVGContentUtils::ParseNumber(mIter, mEnd, r2) && SkipCommaWsp() && + SVGContentUtils::ParseNumber(mIter, mEnd, angle) && SkipCommaWsp() && + ParseFlag(largeArcFlag) && SkipCommaWsp() && ParseFlag(sweepFlag) && + SkipCommaWsp() && ParseCoordPair(x, y))) { + return false; + } + + // We can only pass floats after 'type', and per the SVG spec for arc, + // non-zero args are treated at 'true'. + if (NS_FAILED(mPathSegList->AppendSeg( + aAbsCoords ? PATHSEG_ARC_ABS : PATHSEG_ARC_REL, r1, r2, angle, + largeArcFlag ? 1.0f : 0.0f, sweepFlag ? 1.0f : 0.0f, x, y))) { + return false; + } + + if (!SkipWsp() || IsAlpha(*mIter)) { + // End of data, or start of a new command + return true; + } + SkipCommaWsp(); + } +} + +//----------------------------------------------------------------------- + +static double CalcVectorAngle(double ux, double uy, double vx, double vy) { + double ta = atan2(uy, ux); + double tb = atan2(vy, vx); + if (tb >= ta) return tb - ta; + return 2 * M_PI - (ta - tb); +} + +SVGArcConverter::SVGArcConverter(const Point& from, const Point& to, + const Point& radii, double angle, + bool largeArcFlag, bool sweepFlag) { + MOZ_ASSERT(radii.x != 0.0f && radii.y != 0.0f, "Bad radii"); + + const double radPerDeg = M_PI / 180.0; + mSegIndex = 0; + + if (from == to) { + mNumSegs = 0; + return; + } + + // Convert to center parameterization as shown in + // http://www.w3.org/TR/SVG/implnote.html + mRx = fabs(radii.x); + mRy = fabs(radii.y); + + mSinPhi = sin(angle * radPerDeg); + mCosPhi = cos(angle * radPerDeg); + + double x1dash = + mCosPhi * (from.x - to.x) / 2.0 + mSinPhi * (from.y - to.y) / 2.0; + double y1dash = + -mSinPhi * (from.x - to.x) / 2.0 + mCosPhi * (from.y - to.y) / 2.0; + + double root; + double numerator = mRx * mRx * mRy * mRy - mRx * mRx * y1dash * y1dash - + mRy * mRy * x1dash * x1dash; + + if (numerator < 0.0) { + // If mRx , mRy and are such that there is no solution (basically, + // the ellipse is not big enough to reach from 'from' to 'to' + // then the ellipse is scaled up uniformly until there is + // exactly one solution (until the ellipse is just big enough). + + // -> find factor s, such that numerator' with mRx'=s*mRx and + // mRy'=s*mRy becomes 0 : + double s = sqrt(1.0 - numerator / (mRx * mRx * mRy * mRy)); + + mRx *= s; + mRy *= s; + root = 0.0; + + } else { + root = (largeArcFlag == sweepFlag ? -1.0 : 1.0) * + sqrt(numerator / + (mRx * mRx * y1dash * y1dash + mRy * mRy * x1dash * x1dash)); + } + + double cxdash = root * mRx * y1dash / mRy; + double cydash = -root * mRy * x1dash / mRx; + + mC.x = mCosPhi * cxdash - mSinPhi * cydash + (from.x + to.x) / 2.0; + mC.y = mSinPhi * cxdash + mCosPhi * cydash + (from.y + to.y) / 2.0; + mTheta = CalcVectorAngle(1.0, 0.0, (x1dash - cxdash) / mRx, + (y1dash - cydash) / mRy); + double dtheta = + CalcVectorAngle((x1dash - cxdash) / mRx, (y1dash - cydash) / mRy, + (-x1dash - cxdash) / mRx, (-y1dash - cydash) / mRy); + if (!sweepFlag && dtheta > 0) + dtheta -= 2.0 * M_PI; + else if (sweepFlag && dtheta < 0) + dtheta += 2.0 * M_PI; + + // Convert into cubic bezier segments <= 90deg + mNumSegs = static_cast(ceil(fabs(dtheta / (M_PI / 2.0)))); + mDelta = dtheta / mNumSegs; + mT = 8.0 / 3.0 * sin(mDelta / 4.0) * sin(mDelta / 4.0) / sin(mDelta / 2.0); + + mFrom = from; +} + +bool SVGArcConverter::GetNextSegment(Point* cp1, Point* cp2, Point* to) { + if (mSegIndex == mNumSegs) { + return false; + } + + double cosTheta1 = cos(mTheta); + double sinTheta1 = sin(mTheta); + double theta2 = mTheta + mDelta; + double cosTheta2 = cos(theta2); + double sinTheta2 = sin(theta2); + + // a) calculate endpoint of the segment: + to->x = mCosPhi * mRx * cosTheta2 - mSinPhi * mRy * sinTheta2 + mC.x; + to->y = mSinPhi * mRx * cosTheta2 + mCosPhi * mRy * sinTheta2 + mC.y; + + // b) calculate gradients at start/end points of segment: + cp1->x = + mFrom.x + mT * (-mCosPhi * mRx * sinTheta1 - mSinPhi * mRy * cosTheta1); + cp1->y = + mFrom.y + mT * (-mSinPhi * mRx * sinTheta1 + mCosPhi * mRy * cosTheta1); + + cp2->x = to->x + mT * (mCosPhi * mRx * sinTheta2 + mSinPhi * mRy * cosTheta2); + cp2->y = to->y + mT * (mSinPhi * mRx * sinTheta2 - mCosPhi * mRy * cosTheta2); + + // do next segment + mTheta = theta2; + mFrom = *to; + ++mSegIndex; + + return true; +} + +} // namespace mozilla diff --git a/dom/svg/SVGPathDataParser.h b/dom/svg/SVGPathDataParser.h new file mode 100644 index 0000000000..02073f7f4e --- /dev/null +++ b/dom/svg/SVGPathDataParser.h @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPATHDATAPARSER_H_ +#define DOM_SVG_SVGPATHDATAPARSER_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/gfx/Point.h" +#include "SVGDataParser.h" + +namespace mozilla { +class SVGPathData; + +//////////////////////////////////////////////////////////////////////// +// SVGPathDataParser: a simple recursive descent parser that builds +// DOMSVGPathSegs from path data strings. The grammar for path data +// can be found in SVG CR 20001102, chapter 8. + +class SVGPathDataParser : public SVGDataParser { + public: + SVGPathDataParser(const nsAString& aValue, mozilla::SVGPathData* aList) + : SVGDataParser(aValue), mPathSegList(aList) { + MOZ_ASSERT(aList, "null path data"); + } + + bool Parse(); + + private: + bool ParseCoordPair(float& aX, float& aY); + bool ParseFlag(bool& aFlag); + + bool ParsePath(); + bool IsStartOfSubPath() const; + bool ParseSubPath(); + + bool ParseSubPathElements(); + bool ParseSubPathElement(char16_t aCommandType, bool aAbsCoords); + + bool ParseMoveto(); + bool ParseClosePath(); + bool ParseLineto(bool aAbsCoords); + bool ParseHorizontalLineto(bool aAbsCoords); + bool ParseVerticalLineto(bool aAbsCoords); + bool ParseCurveto(bool aAbsCoords); + bool ParseSmoothCurveto(bool aAbsCoords); + bool ParseQuadBezierCurveto(bool aAbsCoords); + bool ParseSmoothQuadBezierCurveto(bool aAbsCoords); + bool ParseEllipticalArc(bool aAbsCoords); + + mozilla::SVGPathData* const mPathSegList; +}; + +class SVGArcConverter { + using Point = mozilla::gfx::Point; + + public: + SVGArcConverter(const Point& from, const Point& to, const Point& radii, + double angle, bool largeArcFlag, bool sweepFlag); + bool GetNextSegment(Point* cp1, Point* cp2, Point* to); + + protected: + int32_t mNumSegs, mSegIndex; + double mTheta, mDelta, mT; + double mSinPhi, mCosPhi; + double mRx, mRy; + Point mFrom, mC; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGPATHDATAPARSER_H_ diff --git a/dom/svg/SVGPathElement.cpp b/dom/svg/SVGPathElement.cpp new file mode 100644 index 0000000000..c49c5b9f72 --- /dev/null +++ b/dom/svg/SVGPathElement.cpp @@ -0,0 +1,381 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGPathElement.h" + +#include + +#include "DOMSVGPathSeg.h" +#include "DOMSVGPathSegList.h" +#include "SVGGeometryProperty.h" +#include "gfx2DGlue.h" +#include "gfxPlatform.h" +#include "nsGkAtoms.h" +#include "nsIFrame.h" +#include "nsStyleConsts.h" +#include "nsStyleStruct.h" +#include "nsWindowSizes.h" +#include "mozilla/dom/SVGPathElementBinding.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/SVGContentUtils.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Path) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +JSObject* SVGPathElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGPathElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGPathElement::SVGPathElement( + already_AddRefed&& aNodeInfo) + : SVGPathElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// memory reporting methods + +void SVGPathElement::AddSizeOfExcludingThis(nsWindowSizes& aSizes, + size_t* aNodeSize) const { + SVGPathElementBase::AddSizeOfExcludingThis(aSizes, aNodeSize); + *aNodeSize += mD.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf); +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGPathElement) + +uint32_t SVGPathElement::GetPathSegAtLength(float distance) { + uint32_t seg = 0; + auto callback = [&](const ComputedStyle* s) { + const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset(); + if (styleSVGReset->mD.IsPath()) { + seg = SVGPathData::GetPathSegAtLength( + styleSVGReset->mD.AsPath()._0.AsSpan(), distance); + } + }; + + FlushStyleIfNeeded(); + if (SVGGeometryProperty::DoForComputedStyle(this, callback)) { + return seg; + } + return mD.GetAnimValue().GetPathSegAtLength(distance); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegClosePath() { + RefPtr pathSeg = new DOMSVGPathSegClosePath(); + return pathSeg.forget(); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegMovetoAbs(float x, float y) { + RefPtr pathSeg = new DOMSVGPathSegMovetoAbs(x, y); + return pathSeg.forget(); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegMovetoRel(float x, float y) { + RefPtr pathSeg = new DOMSVGPathSegMovetoRel(x, y); + return pathSeg.forget(); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegLinetoAbs(float x, float y) { + RefPtr pathSeg = new DOMSVGPathSegLinetoAbs(x, y); + return pathSeg.forget(); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegLinetoRel(float x, float y) { + RefPtr pathSeg = new DOMSVGPathSegLinetoRel(x, y); + return pathSeg.forget(); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegCurvetoCubicAbs(float x, float y, float x1, + float y1, float x2, float y2) { + // Note that we swap from DOM API argument order to the argument order used + // in the element's 'd' attribute (i.e. we put the arguments for the + // end point of the segment last instead of first). + RefPtr pathSeg = + new DOMSVGPathSegCurvetoCubicAbs(x1, y1, x2, y2, x, y); + return pathSeg.forget(); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegCurvetoCubicRel(float x, float y, float x1, + float y1, float x2, float y2) { + // See comment in CreateSVGPathSegCurvetoCubicAbs + RefPtr pathSeg = + new DOMSVGPathSegCurvetoCubicRel(x1, y1, x2, y2, x, y); + return pathSeg.forget(); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegCurvetoQuadraticAbs(float x, float y, float x1, + float y1) { + // See comment in CreateSVGPathSegCurvetoCubicAbs + RefPtr pathSeg = + new DOMSVGPathSegCurvetoQuadraticAbs(x1, y1, x, y); + return pathSeg.forget(); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegCurvetoQuadraticRel(float x, float y, float x1, + float y1) { + // See comment in CreateSVGPathSegCurvetoCubicAbs + RefPtr pathSeg = + new DOMSVGPathSegCurvetoQuadraticRel(x1, y1, x, y); + return pathSeg.forget(); +} + +already_AddRefed SVGPathElement::CreateSVGPathSegArcAbs( + float x, float y, float r1, float r2, float angle, bool largeArcFlag, + bool sweepFlag) { + // See comment in CreateSVGPathSegCurvetoCubicAbs + RefPtr pathSeg = + new DOMSVGPathSegArcAbs(r1, r2, angle, largeArcFlag, sweepFlag, x, y); + return pathSeg.forget(); +} + +already_AddRefed SVGPathElement::CreateSVGPathSegArcRel( + float x, float y, float r1, float r2, float angle, bool largeArcFlag, + bool sweepFlag) { + // See comment in CreateSVGPathSegCurvetoCubicAbs + RefPtr pathSeg = + new DOMSVGPathSegArcRel(r1, r2, angle, largeArcFlag, sweepFlag, x, y); + return pathSeg.forget(); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegLinetoHorizontalAbs(float x) { + RefPtr pathSeg = + new DOMSVGPathSegLinetoHorizontalAbs(x); + return pathSeg.forget(); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegLinetoHorizontalRel(float x) { + RefPtr pathSeg = + new DOMSVGPathSegLinetoHorizontalRel(x); + return pathSeg.forget(); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegLinetoVerticalAbs(float y) { + RefPtr pathSeg = + new DOMSVGPathSegLinetoVerticalAbs(y); + return pathSeg.forget(); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegLinetoVerticalRel(float y) { + RefPtr pathSeg = + new DOMSVGPathSegLinetoVerticalRel(y); + return pathSeg.forget(); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegCurvetoCubicSmoothAbs(float x, float y, + float x2, float y2) { + // See comment in CreateSVGPathSegCurvetoCubicAbs + RefPtr pathSeg = + new DOMSVGPathSegCurvetoCubicSmoothAbs(x2, y2, x, y); + return pathSeg.forget(); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegCurvetoCubicSmoothRel(float x, float y, + float x2, float y2) { + // See comment in CreateSVGPathSegCurvetoCubicAbs + RefPtr pathSeg = + new DOMSVGPathSegCurvetoCubicSmoothRel(x2, y2, x, y); + return pathSeg.forget(); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegCurvetoQuadraticSmoothAbs(float x, float y) { + RefPtr pathSeg = + new DOMSVGPathSegCurvetoQuadraticSmoothAbs(x, y); + return pathSeg.forget(); +} + +already_AddRefed +SVGPathElement::CreateSVGPathSegCurvetoQuadraticSmoothRel(float x, float y) { + RefPtr pathSeg = + new DOMSVGPathSegCurvetoQuadraticSmoothRel(x, y); + return pathSeg.forget(); +} + +// FIXME: This API is enabled only if dom.svg.pathSeg.enabled is true. This +// preference is off by default in Bug 1388931, and will be dropped later. +// So we are not planning to map d property for this API. +already_AddRefed SVGPathElement::PathSegList() { + return DOMSVGPathSegList::GetDOMWrapper(mD.GetBaseValKey(), this, false); +} + +// FIXME: This API is enabled only if dom.svg.pathSeg.enabled is true. This +// preference is off by default in Bug 1388931, and will be dropped later. +// So we are not planning to map d property for this API. +already_AddRefed SVGPathElement::AnimatedPathSegList() { + return DOMSVGPathSegList::GetDOMWrapper(mD.GetAnimValKey(), this, true); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +bool SVGPathElement::HasValidDimensions() const { + bool hasPath = false; + auto callback = [&](const ComputedStyle* s) { + const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset(); + hasPath = + styleSVGReset->mD.IsPath() && !styleSVGReset->mD.AsPath()._0.IsEmpty(); + }; + + SVGGeometryProperty::DoForComputedStyle(this, callback); + // If hasPath is false, we may disable the pref of d property, so we fallback + // to check mD. + return hasPath || !mD.GetAnimValue().IsEmpty(); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +NS_IMETHODIMP_(bool) +SVGPathElement::IsAttributeMapped(const nsAtom* name) const { + return name == nsGkAtoms::d || SVGPathElementBase::IsAttributeMapped(name); +} + +already_AddRefed SVGPathElement::GetOrBuildPathForMeasuring() { + RefPtr path; + bool success = SVGGeometryProperty::DoForComputedStyle( + this, [&path](const ComputedStyle* s) { + const auto& d = s->StyleSVGReset()->mD; + if (d.IsNone()) { + return; + } + path = SVGPathData::BuildPathForMeasuring(d.AsPath()._0.AsSpan()); + }); + return success ? path.forget() : mD.GetAnimValue().BuildPathForMeasuring(); +} + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +bool SVGPathElement::AttributeDefinesGeometry(const nsAtom* aName) { + return aName == nsGkAtoms::d || aName == nsGkAtoms::pathLength; +} + +bool SVGPathElement::IsMarkable() { return true; } + +void SVGPathElement::GetMarkPoints(nsTArray* aMarks) { + auto callback = [aMarks](const ComputedStyle* s) { + const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset(); + if (styleSVGReset->mD.IsPath()) { + Span path = + styleSVGReset->mD.AsPath()._0.AsSpan(); + SVGPathData::GetMarkerPositioningData(path, aMarks); + } + }; + + if (SVGGeometryProperty::DoForComputedStyle(this, callback)) { + return; + } + + mD.GetAnimValue().GetMarkerPositioningData(aMarks); +} + +void SVGPathElement::GetAsSimplePath(SimplePath* aSimplePath) { + aSimplePath->Reset(); + auto callback = [&](const ComputedStyle* s) { + const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset(); + if (styleSVGReset->mD.IsPath()) { + auto pathData = styleSVGReset->mD.AsPath()._0.AsSpan(); + auto maybeRect = SVGPathToAxisAlignedRect(pathData); + if (maybeRect.isSome()) { + Rect r = maybeRect.value(); + aSimplePath->SetRect(r.x, r.y, r.width, r.height); + } + } + }; + + SVGGeometryProperty::DoForComputedStyle(this, callback); +} + +already_AddRefed SVGPathElement::BuildPath(PathBuilder* aBuilder) { + // The Moz2D PathBuilder that our SVGPathData will be using only cares about + // the fill rule. However, in order to fulfill the requirements of the SVG + // spec regarding zero length sub-paths when square line caps are in use, + // SVGPathData needs to know our stroke-linecap style and, if "square", then + // also our stroke width. See the comment for + // ApproximateZeroLengthSubpathSquareCaps for more info. + + auto strokeLineCap = StyleStrokeLinecap::Butt; + Float strokeWidth = 0; + RefPtr path; + + auto callback = [&](const ComputedStyle* s) { + const nsStyleSVG* styleSVG = s->StyleSVG(); + // Note: the path that we return may be used for hit-testing, and SVG + // exposes hit-testing of strokes that are not actually painted. For that + // reason we do not check for eStyleSVGPaintType_None or check the stroke + // opacity here. + if (styleSVG->mStrokeLinecap != StyleStrokeLinecap::Butt) { + strokeLineCap = styleSVG->mStrokeLinecap; + strokeWidth = SVGContentUtils::GetStrokeWidth(this, s, nullptr); + } + + const auto& d = s->StyleSVGReset()->mD; + if (d.IsPath()) { + path = SVGPathData::BuildPath(d.AsPath()._0.AsSpan(), aBuilder, + strokeLineCap, strokeWidth); + } + }; + + bool success = SVGGeometryProperty::DoForComputedStyle(this, callback); + if (success) { + return path.forget(); + } + + // Fallback to use the d attribute if it exists. + return mD.GetAnimValue().BuildPath(aBuilder, strokeLineCap, strokeWidth); +} + +bool SVGPathElement::GetDistancesFromOriginToEndsOfVisibleSegments( + FallibleTArray* aOutput) { + bool ret = false; + auto callback = [&ret, aOutput](const ComputedStyle* s) { + const auto& d = s->StyleSVGReset()->mD; + ret = d.IsNone() || + SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments( + d.AsPath()._0.AsSpan(), aOutput); + }; + + if (SVGGeometryProperty::DoForComputedStyle(this, callback)) { + return ret; + } + + return mD.GetAnimValue().GetDistancesFromOriginToEndsOfVisibleSegments( + aOutput); +} + +/* static */ +bool SVGPathElement::IsDPropertyChangedViaCSS(const ComputedStyle& aNewStyle, + const ComputedStyle& aOldStyle) { + return aNewStyle.StyleSVGReset()->mD != aOldStyle.StyleSVGReset()->mD; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGPathElement.h b/dom/svg/SVGPathElement.h new file mode 100644 index 0000000000..d377755917 --- /dev/null +++ b/dom/svg/SVGPathElement.h @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPATHELEMENT_H_ +#define DOM_SVG_SVGPATHELEMENT_H_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "SVGAnimatedPathSegList.h" +#include "SVGGeometryElement.h" +#include "DOMSVGPathSeg.h" + +nsresult NS_NewSVGPathElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGPathElementBase = SVGGeometryElement; + +class SVGPathElement final : public SVGPathElementBase { + using Path = mozilla::gfx::Path; + + protected: + friend nsresult(::NS_NewSVGPathElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + explicit SVGPathElement(already_AddRefed&& aNodeInfo); + + void GetAsSimplePath(SimplePath* aSimplePath) override; + + public: + NS_DECL_ADDSIZEOFEXCLUDINGTHIS + + // nsIContent interface + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* name) const override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + // SVGGeometryElement methods: + bool AttributeDefinesGeometry(const nsAtom* aName) override; + bool IsMarkable() override; + void GetMarkPoints(nsTArray* aMarks) override; + /* + * Note: This function maps d attribute to CSS d property, and we don't flush + * style in this function because some callers don't need it, so if the caller + * needs style to be flushed (e.g. DOM APIs), the caller should flush style + * before calling this. + */ + already_AddRefed BuildPath(PathBuilder* aBuilder) override; + + /** + * This returns a path without the extra little line segments that + * ApproximateZeroLengthSubpathSquareCaps can insert if we have square-caps. + * See the comment for that function for more info on that. + * + * Note: This function maps d attribute to CSS d property, and we don't flush + * style in this function because some callers don't need it, so if the caller + * needs style to be flushed (e.g. DOM APIs), the caller should flush style + * before calling this. + */ + already_AddRefed GetOrBuildPathForMeasuring() override; + + bool GetDistancesFromOriginToEndsOfVisibleSegments( + FallibleTArray* aOutput) override; + + // nsIContent interface + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + SVGAnimatedPathSegList* GetAnimPathSegList() override { return &mD; } + + nsStaticAtom* GetPathDataAttrName() const override { return nsGkAtoms::d; } + + // WebIDL + MOZ_CAN_RUN_SCRIPT uint32_t GetPathSegAtLength(float distance); + already_AddRefed CreateSVGPathSegClosePath(); + already_AddRefed CreateSVGPathSegMovetoAbs(float x, + float y); + already_AddRefed CreateSVGPathSegMovetoRel(float x, + float y); + already_AddRefed CreateSVGPathSegLinetoAbs(float x, + float y); + already_AddRefed CreateSVGPathSegLinetoRel(float x, + float y); + already_AddRefed + CreateSVGPathSegCurvetoCubicAbs(float x, float y, float x1, float y1, + float x2, float y2); + already_AddRefed + CreateSVGPathSegCurvetoCubicRel(float x, float y, float x1, float y1, + float x2, float y2); + already_AddRefed + CreateSVGPathSegCurvetoQuadraticAbs(float x, float y, float x1, float y1); + already_AddRefed + CreateSVGPathSegCurvetoQuadraticRel(float x, float y, float x1, float y1); + already_AddRefed CreateSVGPathSegArcAbs( + float x, float y, float r1, float r2, float angle, bool largeArcFlag, + bool sweepFlag); + already_AddRefed CreateSVGPathSegArcRel( + float x, float y, float r1, float r2, float angle, bool largeArcFlag, + bool sweepFlag); + already_AddRefed + CreateSVGPathSegLinetoHorizontalAbs(float x); + already_AddRefed + CreateSVGPathSegLinetoHorizontalRel(float x); + already_AddRefed + CreateSVGPathSegLinetoVerticalAbs(float y); + already_AddRefed + CreateSVGPathSegLinetoVerticalRel(float y); + already_AddRefed + CreateSVGPathSegCurvetoCubicSmoothAbs(float x, float y, float x2, float y2); + already_AddRefed + CreateSVGPathSegCurvetoCubicSmoothRel(float x, float y, float x2, float y2); + already_AddRefed + CreateSVGPathSegCurvetoQuadraticSmoothAbs(float x, float y); + already_AddRefed + CreateSVGPathSegCurvetoQuadraticSmoothRel(float x, float y); + already_AddRefed PathSegList(); + already_AddRefed AnimatedPathSegList(); + + static bool IsDPropertyChangedViaCSS(const ComputedStyle& aNewStyle, + const ComputedStyle& aOldStyle); + + protected: + SVGAnimatedPathSegList mD; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGPATHELEMENT_H_ diff --git a/dom/svg/SVGPathSegListSMILType.cpp b/dom/svg/SVGPathSegListSMILType.cpp new file mode 100644 index 0000000000..613e1bc83d --- /dev/null +++ b/dom/svg/SVGPathSegListSMILType.cpp @@ -0,0 +1,466 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGPathSegListSMILType.h" + +#include "mozilla/DebugOnly.h" +#include "mozilla/SMILValue.h" +#include "SVGPathData.h" +#include "SVGPathSegUtils.h" + +using namespace mozilla::dom::SVGPathSeg_Binding; + +// Indices of boolean flags within 'arc' segment chunks in path-data arrays +// (where '0' would correspond to the index of the encoded segment type): +#define LARGE_ARC_FLAG_IDX 4 +#define SWEEP_FLAG_IDX 5 + +namespace mozilla { + +//---------------------------------------------------------------------- +// nsISMILType implementation + +void SVGPathSegListSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + aValue.mU.mPtr = new SVGPathDataAndInfo(); + aValue.mType = this; +} + +void SVGPathSegListSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value type"); + delete static_cast(aValue.mU.mPtr); + aValue.mU.mPtr = nullptr; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGPathSegListSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value"); + + const SVGPathDataAndInfo* src = + static_cast(aSrc.mU.mPtr); + SVGPathDataAndInfo* dest = static_cast(aDest.mU.mPtr); + + return dest->CopyFrom(*src); +} + +bool SVGPathSegListSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected type for SMIL value"); + + return *static_cast(aLeft.mU.mPtr) == + *static_cast(aRight.mU.mPtr); +} + +static bool ArcFlagsDiffer(SVGPathDataAndInfo::const_iterator aPathData1, + SVGPathDataAndInfo::const_iterator aPathData2) { + MOZ_ASSERT( + SVGPathSegUtils::IsArcType(SVGPathSegUtils::DecodeType(aPathData1[0])), + "ArcFlagsDiffer called with non-arc segment"); + MOZ_ASSERT( + SVGPathSegUtils::IsArcType(SVGPathSegUtils::DecodeType(aPathData2[0])), + "ArcFlagsDiffer called with non-arc segment"); + + return aPathData1[LARGE_ARC_FLAG_IDX] != aPathData2[LARGE_ARC_FLAG_IDX] || + aPathData1[SWEEP_FLAG_IDX] != aPathData2[SWEEP_FLAG_IDX]; +} + +enum PathInterpolationResult { + eCannotInterpolate, + eRequiresConversion, + eCanInterpolate +}; + +static PathInterpolationResult CanInterpolate(const SVGPathDataAndInfo& aStart, + const SVGPathDataAndInfo& aEnd) { + if (aStart.IsIdentity()) { + return eCanInterpolate; + } + + if (aStart.Length() != aEnd.Length()) { + return eCannotInterpolate; + } + + PathInterpolationResult result = eCanInterpolate; + + SVGPathDataAndInfo::const_iterator pStart = aStart.begin(); + SVGPathDataAndInfo::const_iterator pEnd = aEnd.begin(); + SVGPathDataAndInfo::const_iterator pStartDataEnd = aStart.end(); + SVGPathDataAndInfo::const_iterator pEndDataEnd = aEnd.end(); + + while (pStart < pStartDataEnd && pEnd < pEndDataEnd) { + uint32_t startType = SVGPathSegUtils::DecodeType(*pStart); + uint32_t endType = SVGPathSegUtils::DecodeType(*pEnd); + + if (SVGPathSegUtils::IsArcType(startType) && + SVGPathSegUtils::IsArcType(endType) && ArcFlagsDiffer(pStart, pEnd)) { + return eCannotInterpolate; + } + + if (startType != endType) { + if (!SVGPathSegUtils::SameTypeModuloRelativeness(startType, endType)) { + return eCannotInterpolate; + } + + result = eRequiresConversion; + } + + pStart += 1 + SVGPathSegUtils::ArgCountForType(startType); + pEnd += 1 + SVGPathSegUtils::ArgCountForType(endType); + } + + MOZ_ASSERT(pStart <= pStartDataEnd && pEnd <= pEndDataEnd, + "Iterated past end of buffer! (Corrupt path data?)"); + + if (pStart != pStartDataEnd || pEnd != pEndDataEnd) { + return eCannotInterpolate; + } + + return result; +} + +enum RelativenessAdjustmentType { eAbsoluteToRelative, eRelativeToAbsolute }; + +static inline void AdjustSegmentForRelativeness( + RelativenessAdjustmentType aAdjustmentType, + const SVGPathDataAndInfo::iterator& aSegmentToAdjust, + const SVGPathTraversalState& aState) { + if (aAdjustmentType == eAbsoluteToRelative) { + aSegmentToAdjust[0] -= aState.pos.x; + aSegmentToAdjust[1] -= aState.pos.y; + } else { + aSegmentToAdjust[0] += aState.pos.x; + aSegmentToAdjust[1] += aState.pos.y; + } +} + +/** + * Helper function for AddWeightedPathSegLists, to add multiples of two + * path-segments of the same type. + * + * NOTE: |aSeg1| is allowed to be nullptr, so we use |aSeg2| as the + * authoritative source of things like segment-type and boolean arc flags. + * + * @param aCoeff1 The coefficient to use on the first segment. + * @param aSeg1 An iterator pointing to the first segment. This can be + * null, which is treated as identity (zero). + * @param aCoeff2 The coefficient to use on the second segment. + * @param aSeg2 An iterator pointing to the second segment. + * @param [out] aResultSeg An iterator pointing to where we should write the + * result of this operation. + */ +static inline void AddWeightedPathSegs( + double aCoeff1, SVGPathDataAndInfo::const_iterator& aSeg1, double aCoeff2, + SVGPathDataAndInfo::const_iterator& aSeg2, + SVGPathDataAndInfo::iterator& aResultSeg) { + MOZ_ASSERT(aSeg2, "2nd segment must be non-null"); + MOZ_ASSERT(aResultSeg, "result segment must be non-null"); + + uint32_t segType = SVGPathSegUtils::DecodeType(aSeg2[0]); + MOZ_ASSERT(!aSeg1 || SVGPathSegUtils::DecodeType(*aSeg1) == segType, + "unexpected segment type"); + + // FIRST: Directly copy the arguments that don't make sense to add. + aResultSeg[0] = aSeg2[0]; // encoded segment type + + bool isArcType = SVGPathSegUtils::IsArcType(segType); + if (isArcType) { + // Copy boolean arc flags. + MOZ_ASSERT(!aSeg1 || !ArcFlagsDiffer(aSeg1, aSeg2), + "Expecting arc flags to match"); + aResultSeg[LARGE_ARC_FLAG_IDX] = aSeg2[LARGE_ARC_FLAG_IDX]; + aResultSeg[SWEEP_FLAG_IDX] = aSeg2[SWEEP_FLAG_IDX]; + } + + // SECOND: Add the arguments that are supposed to be added. + // (The 1's below are to account for segment type) + uint32_t numArgs = SVGPathSegUtils::ArgCountForType(segType); + for (uint32_t i = 1; i < 1 + numArgs; ++i) { + // Need to skip arc flags for arc-type segments. (already handled them) + if (!(isArcType && (i == LARGE_ARC_FLAG_IDX || i == SWEEP_FLAG_IDX))) { + aResultSeg[i] = (aSeg1 ? aCoeff1 * aSeg1[i] : 0.0) + aCoeff2 * aSeg2[i]; + } + } + + // FINALLY: Shift iterators forward. ("1+" is to include seg-type) + if (aSeg1) { + aSeg1 += 1 + numArgs; + } + aSeg2 += 1 + numArgs; + aResultSeg += 1 + numArgs; +} + +/** + * Helper function for Add & Interpolate, to add multiples of two path-segment + * lists. + * + * NOTE: aList1 and aList2 are assumed to have their segment-types and + * segment-count match exactly (unless aList1 is an identity value). + * + * NOTE: aResult, the output list, is expected to either be an identity value + * (in which case we'll grow it) *or* to already have the exactly right length + * (e.g. in cases where aList1 and aResult are actually the same list). + * + * @param aCoeff1 The coefficient to use on the first path segment list. + * @param aList1 The first path segment list. Allowed to be identity. + * @param aCoeff2 The coefficient to use on the second path segment list. + * @param aList2 The second path segment list. + * @param [out] aResultSeg The resulting path segment list. Allowed to be + * identity, in which case we'll grow it to the right + * size. Also allowed to be the same list as aList1. + */ +static nsresult AddWeightedPathSegLists(double aCoeff1, + const SVGPathDataAndInfo& aList1, + double aCoeff2, + const SVGPathDataAndInfo& aList2, + SVGPathDataAndInfo& aResult) { + MOZ_ASSERT(aCoeff1 >= 0.0 && aCoeff2 >= 0.0, + "expecting non-negative coefficients"); + MOZ_ASSERT(!aList2.IsIdentity(), "expecting 2nd list to be non-identity"); + MOZ_ASSERT(aList1.IsIdentity() || aList1.Length() == aList2.Length(), + "expecting 1st list to be identity or to have same " + "length as 2nd list"); + MOZ_ASSERT(aResult.IsIdentity() || aResult.Length() == aList2.Length(), + "expecting result list to be identity or to have same " + "length as 2nd list"); + + SVGPathDataAndInfo::const_iterator iter1, end1; + if (aList1.IsIdentity()) { + iter1 = end1 = nullptr; // indicate that this is an identity list + } else { + iter1 = aList1.begin(); + end1 = aList1.end(); + } + SVGPathDataAndInfo::const_iterator iter2 = aList2.begin(); + SVGPathDataAndInfo::const_iterator end2 = aList2.end(); + + // Grow |aResult| if necessary. (NOTE: It's possible that aResult and aList1 + // are the same list, so this may implicitly resize aList1. That's fine, + // because in that case, we will have already set iter1 to nullptr above, to + // record that our first operand is an identity value.) + if (aResult.IsIdentity()) { + if (!aResult.SetLength(aList2.Length())) { + return NS_ERROR_OUT_OF_MEMORY; + } + aResult.SetElement(aList2.Element()); // propagate target element info! + } + + SVGPathDataAndInfo::iterator resultIter = aResult.begin(); + + while ((!iter1 || iter1 != end1) && iter2 != end2) { + AddWeightedPathSegs(aCoeff1, iter1, aCoeff2, iter2, resultIter); + } + MOZ_ASSERT( + (!iter1 || iter1 == end1) && iter2 == end2 && resultIter == aResult.end(), + "Very, very bad - path data corrupt"); + return NS_OK; +} + +static void ConvertPathSegmentData(SVGPathDataAndInfo::const_iterator& aStart, + SVGPathDataAndInfo::const_iterator& aEnd, + SVGPathDataAndInfo::iterator& aResult, + SVGPathTraversalState& aState) { + uint32_t startType = SVGPathSegUtils::DecodeType(*aStart); + uint32_t endType = SVGPathSegUtils::DecodeType(*aEnd); + + uint32_t segmentLengthIncludingType = + 1 + SVGPathSegUtils::ArgCountForType(startType); + + SVGPathDataAndInfo::const_iterator pResultSegmentBegin = aResult; + + if (startType == endType) { + // No conversion need, just directly copy aStart. + aEnd += segmentLengthIncludingType; + while (segmentLengthIncludingType) { + *aResult++ = *aStart++; + --segmentLengthIncludingType; + } + SVGPathSegUtils::TraversePathSegment(pResultSegmentBegin, aState); + return; + } + + MOZ_ASSERT( + SVGPathSegUtils::SameTypeModuloRelativeness(startType, endType), + "Incompatible path segment types passed to ConvertPathSegmentData!"); + + RelativenessAdjustmentType adjustmentType = + SVGPathSegUtils::IsRelativeType(startType) ? eRelativeToAbsolute + : eAbsoluteToRelative; + + MOZ_ASSERT( + segmentLengthIncludingType == + 1 + SVGPathSegUtils::ArgCountForType(endType), + "Compatible path segment types for interpolation had different lengths!"); + + aResult[0] = aEnd[0]; + + switch (endType) { + case PATHSEG_LINETO_HORIZONTAL_ABS: + case PATHSEG_LINETO_HORIZONTAL_REL: + aResult[1] = + aStart[1] + + (adjustmentType == eRelativeToAbsolute ? 1 : -1) * aState.pos.x; + break; + case PATHSEG_LINETO_VERTICAL_ABS: + case PATHSEG_LINETO_VERTICAL_REL: + aResult[1] = + aStart[1] + + (adjustmentType == eRelativeToAbsolute ? 1 : -1) * aState.pos.y; + break; + case PATHSEG_ARC_ABS: + case PATHSEG_ARC_REL: + aResult[1] = aStart[1]; + aResult[2] = aStart[2]; + aResult[3] = aStart[3]; + aResult[4] = aStart[4]; + aResult[5] = aStart[5]; + aResult[6] = aStart[6]; + aResult[7] = aStart[7]; + AdjustSegmentForRelativeness(adjustmentType, aResult + 6, aState); + break; + case PATHSEG_CURVETO_CUBIC_ABS: + case PATHSEG_CURVETO_CUBIC_REL: + aResult[5] = aStart[5]; + aResult[6] = aStart[6]; + AdjustSegmentForRelativeness(adjustmentType, aResult + 5, aState); + [[fallthrough]]; + case PATHSEG_CURVETO_QUADRATIC_ABS: + case PATHSEG_CURVETO_QUADRATIC_REL: + case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS: + case PATHSEG_CURVETO_CUBIC_SMOOTH_REL: + aResult[3] = aStart[3]; + aResult[4] = aStart[4]; + AdjustSegmentForRelativeness(adjustmentType, aResult + 3, aState); + [[fallthrough]]; + case PATHSEG_MOVETO_ABS: + case PATHSEG_MOVETO_REL: + case PATHSEG_LINETO_ABS: + case PATHSEG_LINETO_REL: + case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS: + case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL: + aResult[1] = aStart[1]; + aResult[2] = aStart[2]; + AdjustSegmentForRelativeness(adjustmentType, aResult + 1, aState); + break; + } + + SVGPathSegUtils::TraversePathSegment(pResultSegmentBegin, aState); + aStart += segmentLengthIncludingType; + aEnd += segmentLengthIncludingType; + aResult += segmentLengthIncludingType; +} + +static void ConvertAllPathSegmentData( + SVGPathDataAndInfo::const_iterator aStart, + SVGPathDataAndInfo::const_iterator aStartDataEnd, + SVGPathDataAndInfo::const_iterator aEnd, + SVGPathDataAndInfo::const_iterator aEndDataEnd, + SVGPathDataAndInfo::iterator aResult) { + SVGPathTraversalState state; + state.mode = SVGPathTraversalState::eUpdateOnlyStartAndCurrentPos; + while (aStart < aStartDataEnd && aEnd < aEndDataEnd) { + ConvertPathSegmentData(aStart, aEnd, aResult, state); + } + MOZ_ASSERT(aStart == aStartDataEnd && aEnd == aEndDataEnd, + "Failed to convert all path segment data! (Corrupt?)"); +} + +nsresult SVGPathSegListSMILType::Add(SMILValue& aDest, + const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aValueToAdd.mType == this, "Incompatible SMIL type"); + + SVGPathDataAndInfo& dest = *static_cast(aDest.mU.mPtr); + const SVGPathDataAndInfo& valueToAdd = + *static_cast(aValueToAdd.mU.mPtr); + + if (valueToAdd.IsIdentity()) { // Adding identity value - no-op + return NS_OK; + } + + if (!dest.IsIdentity()) { + // Neither value is identity; make sure they're compatible. + MOZ_ASSERT(dest.Element() == valueToAdd.Element(), + "adding values from different elements...?"); + + PathInterpolationResult check = CanInterpolate(dest, valueToAdd); + if (check == eCannotInterpolate) { + // SVGContentUtils::ReportToConsole - can't add path segment lists with + // different numbers of segments, with arcs that have different flag + // values, or with incompatible segment types. + return NS_ERROR_FAILURE; + } + if (check == eRequiresConversion) { + // Convert dest, in-place, to match the types in valueToAdd: + ConvertAllPathSegmentData(dest.begin(), dest.end(), valueToAdd.begin(), + valueToAdd.end(), dest.begin()); + } + } + + return AddWeightedPathSegLists(1.0, dest, aCount, valueToAdd, dest); +} + +nsresult SVGPathSegListSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aTo.mType == this, "Incompatible SMIL type"); + + // See https://bugzilla.mozilla.org/show_bug.cgi?id=522306#c18 + + // SVGContentUtils::ReportToConsole + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult SVGPathSegListSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + + const SVGPathDataAndInfo& start = + *static_cast(aStartVal.mU.mPtr); + const SVGPathDataAndInfo& end = + *static_cast(aEndVal.mU.mPtr); + SVGPathDataAndInfo& result = + *static_cast(aResult.mU.mPtr); + MOZ_ASSERT(result.IsIdentity(), + "expecting outparam to start out as identity"); + + PathInterpolationResult check = CanInterpolate(start, end); + + if (check == eCannotInterpolate) { + // SVGContentUtils::ReportToConsole - can't interpolate path segment lists + // with different numbers of segments, with arcs with different flag values, + // or with incompatible segment types. + return NS_ERROR_FAILURE; + } + + const SVGPathDataAndInfo* startListToUse = &start; + if (check == eRequiresConversion) { + // Can't convert |start| in-place, since it's const. Instead, we copy it + // into |result|, converting the types as we go, and use that as our start. + if (!result.SetLength(end.Length())) { + return NS_ERROR_OUT_OF_MEMORY; + } + result.SetElement(end.Element()); // propagate target element info! + + ConvertAllPathSegmentData(start.begin(), start.end(), end.begin(), + end.end(), result.begin()); + startListToUse = &result; + } + + return AddWeightedPathSegLists(1.0 - aUnitDistance, *startListToUse, + aUnitDistance, end, result); +} + +} // namespace mozilla diff --git a/dom/svg/SVGPathSegListSMILType.h b/dom/svg/SVGPathSegListSMILType.h new file mode 100644 index 0000000000..172701caca --- /dev/null +++ b/dom/svg/SVGPathSegListSMILType.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPATHSEGLISTSMILTYPE_H_ +#define DOM_SVG_SVGPATHSEGLISTSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +namespace mozilla { + +class SMILValue; + +//////////////////////////////////////////////////////////////////////// +// SVGPathSegListSMILType +// +// Operations for animating an SVGPathData. +// +class SVGPathSegListSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGPathSegListSMILType* Singleton() { + static SVGPathSegListSMILType sSingleton; + return &sSingleton; + } + + protected: + // SMILType Methods + // ------------------- + + void Init(SMILValue& aValue) const override; + + void Destroy(SMILValue& aValue) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGPathSegListSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGPATHSEGLISTSMILTYPE_H_ diff --git a/dom/svg/SVGPathSegUtils.cpp b/dom/svg/SVGPathSegUtils.cpp new file mode 100644 index 0000000000..5300bd65b4 --- /dev/null +++ b/dom/svg/SVGPathSegUtils.cpp @@ -0,0 +1,810 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGPathSegUtils.h" + +#include "mozilla/ArrayUtils.h" // MOZ_ARRAY_LENGTH +#include "mozilla/ServoStyleConsts.h" // StylePathCommand +#include "gfx2DGlue.h" +#include "SVGPathDataParser.h" +#include "nsMathUtils.h" +#include "nsTextFormatter.h" + +using namespace mozilla::dom::SVGPathSeg_Binding; +using namespace mozilla::gfx; + +namespace mozilla { + +static const float PATH_SEG_LENGTH_TOLERANCE = 0.0000001f; +static const uint32_t MAX_RECURSION = 10; + +/* static */ +void SVGPathSegUtils::GetValueAsString(const float* aSeg, nsAString& aValue) { + // Adding new seg type? Is the formatting below acceptable for the new types? + static_assert( + NS_SVG_PATH_SEG_LAST_VALID_TYPE == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, + "Update GetValueAsString for the new value."); + static_assert(NS_SVG_PATH_SEG_MAX_ARGS == 7, + "Add another case to the switch below."); + + uint32_t type = DecodeType(aSeg[0]); + char16_t typeAsChar = GetPathSegTypeAsLetter(type); + + // Special case arcs: + if (IsArcType(type)) { + bool largeArcFlag = aSeg[4] != 0.0f; + bool sweepFlag = aSeg[5] != 0.0f; + nsTextFormatter::ssprintf(aValue, u"%c%g,%g %g %d,%d %g,%g", typeAsChar, + aSeg[1], aSeg[2], aSeg[3], largeArcFlag, + sweepFlag, aSeg[6], aSeg[7]); + } else { + switch (ArgCountForType(type)) { + case 0: + aValue = typeAsChar; + break; + + case 1: + nsTextFormatter::ssprintf(aValue, u"%c%g", typeAsChar, aSeg[1]); + break; + + case 2: + nsTextFormatter::ssprintf(aValue, u"%c%g,%g", typeAsChar, aSeg[1], + aSeg[2]); + break; + + case 4: + nsTextFormatter::ssprintf(aValue, u"%c%g,%g %g,%g", typeAsChar, aSeg[1], + aSeg[2], aSeg[3], aSeg[4]); + break; + + case 6: + nsTextFormatter::ssprintf(aValue, u"%c%g,%g %g,%g %g,%g", typeAsChar, + aSeg[1], aSeg[2], aSeg[3], aSeg[4], aSeg[5], + aSeg[6]); + break; + + default: + MOZ_ASSERT(false, "Unknown segment type"); + aValue = u""; + return; + } + } +} + +static float CalcDistanceBetweenPoints(const Point& aP1, const Point& aP2) { + return NS_hypot(aP2.x - aP1.x, aP2.y - aP1.y); +} + +static void SplitQuadraticBezier(const Point* aCurve, Point* aLeft, + Point* aRight) { + aLeft[0].x = aCurve[0].x; + aLeft[0].y = aCurve[0].y; + aRight[2].x = aCurve[2].x; + aRight[2].y = aCurve[2].y; + aLeft[1].x = (aCurve[0].x + aCurve[1].x) / 2; + aLeft[1].y = (aCurve[0].y + aCurve[1].y) / 2; + aRight[1].x = (aCurve[1].x + aCurve[2].x) / 2; + aRight[1].y = (aCurve[1].y + aCurve[2].y) / 2; + aLeft[2].x = aRight[0].x = (aLeft[1].x + aRight[1].x) / 2; + aLeft[2].y = aRight[0].y = (aLeft[1].y + aRight[1].y) / 2; +} + +static void SplitCubicBezier(const Point* aCurve, Point* aLeft, Point* aRight) { + Point tmp; + tmp.x = (aCurve[1].x + aCurve[2].x) / 4; + tmp.y = (aCurve[1].y + aCurve[2].y) / 4; + aLeft[0].x = aCurve[0].x; + aLeft[0].y = aCurve[0].y; + aRight[3].x = aCurve[3].x; + aRight[3].y = aCurve[3].y; + aLeft[1].x = (aCurve[0].x + aCurve[1].x) / 2; + aLeft[1].y = (aCurve[0].y + aCurve[1].y) / 2; + aRight[2].x = (aCurve[2].x + aCurve[3].x) / 2; + aRight[2].y = (aCurve[2].y + aCurve[3].y) / 2; + aLeft[2].x = aLeft[1].x / 2 + tmp.x; + aLeft[2].y = aLeft[1].y / 2 + tmp.y; + aRight[1].x = aRight[2].x / 2 + tmp.x; + aRight[1].y = aRight[2].y / 2 + tmp.y; + aLeft[3].x = aRight[0].x = (aLeft[2].x + aRight[1].x) / 2; + aLeft[3].y = aRight[0].y = (aLeft[2].y + aRight[1].y) / 2; +} + +static float CalcBezLengthHelper(const Point* aCurve, uint32_t aNumPts, + uint32_t aRecursionCount, + void (*aSplit)(const Point*, Point*, Point*)) { + Point left[4]; + Point right[4]; + float length = 0, dist; + for (uint32_t i = 0; i < aNumPts - 1; i++) { + length += CalcDistanceBetweenPoints(aCurve[i], aCurve[i + 1]); + } + dist = CalcDistanceBetweenPoints(aCurve[0], aCurve[aNumPts - 1]); + if (length - dist > PATH_SEG_LENGTH_TOLERANCE && + aRecursionCount < MAX_RECURSION) { + aSplit(aCurve, left, right); + ++aRecursionCount; + return CalcBezLengthHelper(left, aNumPts, aRecursionCount, aSplit) + + CalcBezLengthHelper(right, aNumPts, aRecursionCount, aSplit); + } + return length; +} + +static inline float CalcLengthOfCubicBezier(const Point& aPos, + const Point& aCP1, + const Point& aCP2, + const Point& aTo) { + Point curve[4] = {aPos, aCP1, aCP2, aTo}; + return CalcBezLengthHelper(curve, 4, 0, SplitCubicBezier); +} + +static inline float CalcLengthOfQuadraticBezier(const Point& aPos, + const Point& aCP, + const Point& aTo) { + Point curve[3] = {aPos, aCP, aTo}; + return CalcBezLengthHelper(curve, 3, 0, SplitQuadraticBezier); +} + +static void TraverseClosePath(const float* aArgs, + SVGPathTraversalState& aState) { + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += CalcDistanceBetweenPoints(aState.pos, aState.start); + aState.cp1 = aState.cp2 = aState.start; + } + aState.pos = aState.start; +} + +static void TraverseMovetoAbs(const float* aArgs, + SVGPathTraversalState& aState) { + aState.start = aState.pos = Point(aArgs[0], aArgs[1]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + // aState.length is unchanged, since move commands don't affect path length. + aState.cp1 = aState.cp2 = aState.start; + } +} + +static void TraverseMovetoRel(const float* aArgs, + SVGPathTraversalState& aState) { + aState.start = aState.pos += Point(aArgs[0], aArgs[1]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + // aState.length is unchanged, since move commands don't affect path length. + aState.cp1 = aState.cp2 = aState.start; + } +} + +static void TraverseLinetoAbs(const float* aArgs, + SVGPathTraversalState& aState) { + Point to(aArgs[0], aArgs[1]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += CalcDistanceBetweenPoints(aState.pos, to); + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseLinetoRel(const float* aArgs, + SVGPathTraversalState& aState) { + Point to = aState.pos + Point(aArgs[0], aArgs[1]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += CalcDistanceBetweenPoints(aState.pos, to); + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseLinetoHorizontalAbs(const float* aArgs, + SVGPathTraversalState& aState) { + Point to(aArgs[0], aState.pos.y); + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += std::fabs(to.x - aState.pos.x); + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseLinetoHorizontalRel(const float* aArgs, + SVGPathTraversalState& aState) { + aState.pos.x += aArgs[0]; + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += std::fabs(aArgs[0]); + aState.cp1 = aState.cp2 = aState.pos; + } +} + +static void TraverseLinetoVerticalAbs(const float* aArgs, + SVGPathTraversalState& aState) { + Point to(aState.pos.x, aArgs[0]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += std::fabs(to.y - aState.pos.y); + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseLinetoVerticalRel(const float* aArgs, + SVGPathTraversalState& aState) { + aState.pos.y += aArgs[0]; + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += std::fabs(aArgs[0]); + aState.cp1 = aState.cp2 = aState.pos; + } +} + +static void TraverseCurvetoCubicAbs(const float* aArgs, + SVGPathTraversalState& aState) { + Point to(aArgs[4], aArgs[5]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp1(aArgs[0], aArgs[1]); + Point cp2(aArgs[2], aArgs[3]); + aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to); + aState.cp2 = cp2; + aState.cp1 = to; + } + aState.pos = to; +} + +static void TraverseCurvetoCubicSmoothAbs(const float* aArgs, + SVGPathTraversalState& aState) { + Point to(aArgs[2], aArgs[3]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp1 = aState.pos - (aState.cp2 - aState.pos); + Point cp2(aArgs[0], aArgs[1]); + aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to); + aState.cp2 = cp2; + aState.cp1 = to; + } + aState.pos = to; +} + +static void TraverseCurvetoCubicRel(const float* aArgs, + SVGPathTraversalState& aState) { + Point to = aState.pos + Point(aArgs[4], aArgs[5]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp1 = aState.pos + Point(aArgs[0], aArgs[1]); + Point cp2 = aState.pos + Point(aArgs[2], aArgs[3]); + aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to); + aState.cp2 = cp2; + aState.cp1 = to; + } + aState.pos = to; +} + +static void TraverseCurvetoCubicSmoothRel(const float* aArgs, + SVGPathTraversalState& aState) { + Point to = aState.pos + Point(aArgs[2], aArgs[3]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp1 = aState.pos - (aState.cp2 - aState.pos); + Point cp2 = aState.pos + Point(aArgs[0], aArgs[1]); + aState.length += (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to); + aState.cp2 = cp2; + aState.cp1 = to; + } + aState.pos = to; +} + +static void TraverseCurvetoQuadraticAbs(const float* aArgs, + SVGPathTraversalState& aState) { + Point to(aArgs[2], aArgs[3]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp(aArgs[0], aArgs[1]); + aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to); + aState.cp1 = cp; + aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseCurvetoQuadraticSmoothAbs(const float* aArgs, + SVGPathTraversalState& aState) { + Point to(aArgs[0], aArgs[1]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp = aState.pos - (aState.cp1 - aState.pos); + aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to); + aState.cp1 = cp; + aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseCurvetoQuadraticRel(const float* aArgs, + SVGPathTraversalState& aState) { + Point to = aState.pos + Point(aArgs[2], aArgs[3]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp = aState.pos + Point(aArgs[0], aArgs[1]); + aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to); + aState.cp1 = cp; + aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseCurvetoQuadraticSmoothRel(const float* aArgs, + SVGPathTraversalState& aState) { + Point to = aState.pos + Point(aArgs[0], aArgs[1]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp = aState.pos - (aState.cp1 - aState.pos); + aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to); + aState.cp1 = cp; + aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseArcAbs(const float* aArgs, SVGPathTraversalState& aState) { + Point to(aArgs[5], aArgs[6]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + float dist = 0; + Point radii(aArgs[0], aArgs[1]); + if (radii.x == 0.0f || radii.y == 0.0f) { + dist = CalcDistanceBetweenPoints(aState.pos, to); + } else { + Point bez[4] = {aState.pos, Point(0, 0), Point(0, 0), Point(0, 0)}; + SVGArcConverter converter(aState.pos, to, radii, aArgs[2], aArgs[3] != 0, + aArgs[4] != 0); + while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) { + dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier); + bez[0] = bez[3]; + } + } + aState.length += dist; + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; +} + +static void TraverseArcRel(const float* aArgs, SVGPathTraversalState& aState) { + Point to = aState.pos + Point(aArgs[5], aArgs[6]); + if (aState.ShouldUpdateLengthAndControlPoints()) { + float dist = 0; + Point radii(aArgs[0], aArgs[1]); + if (radii.x == 0.0f || radii.y == 0.0f) { + dist = CalcDistanceBetweenPoints(aState.pos, to); + } else { + Point bez[4] = {aState.pos, Point(0, 0), Point(0, 0), Point(0, 0)}; + SVGArcConverter converter(aState.pos, to, radii, aArgs[2], aArgs[3] != 0, + aArgs[4] != 0); + while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) { + dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier); + bez[0] = bez[3]; + } + } + aState.length += dist; + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; +} + +using TraverseFunc = void (*)(const float*, SVGPathTraversalState&); + +static TraverseFunc gTraverseFuncTable[NS_SVG_PATH_SEG_TYPE_COUNT] = { + nullptr, // 0 == PATHSEG_UNKNOWN + TraverseClosePath, + TraverseMovetoAbs, + TraverseMovetoRel, + TraverseLinetoAbs, + TraverseLinetoRel, + TraverseCurvetoCubicAbs, + TraverseCurvetoCubicRel, + TraverseCurvetoQuadraticAbs, + TraverseCurvetoQuadraticRel, + TraverseArcAbs, + TraverseArcRel, + TraverseLinetoHorizontalAbs, + TraverseLinetoHorizontalRel, + TraverseLinetoVerticalAbs, + TraverseLinetoVerticalRel, + TraverseCurvetoCubicSmoothAbs, + TraverseCurvetoCubicSmoothRel, + TraverseCurvetoQuadraticSmoothAbs, + TraverseCurvetoQuadraticSmoothRel}; + +/* static */ +void SVGPathSegUtils::TraversePathSegment(const float* aData, + SVGPathTraversalState& aState) { + static_assert( + MOZ_ARRAY_LENGTH(gTraverseFuncTable) == NS_SVG_PATH_SEG_TYPE_COUNT, + "gTraverseFuncTable is out of date"); + uint32_t type = DecodeType(aData[0]); + gTraverseFuncTable[type](aData + 1, aState); +} + +// Basically, this is just a variant version of the above TraverseXXX functions. +// We just put those function inside this and use StylePathCommand instead. +// This function and the above ones should be dropped by Bug 1388931. +/* static */ +void SVGPathSegUtils::TraversePathSegment(const StylePathCommand& aCommand, + SVGPathTraversalState& aState) { + switch (aCommand.tag) { + case StylePathCommand::Tag::ClosePath: + TraverseClosePath(nullptr, aState); + break; + case StylePathCommand::Tag::MoveTo: { + const Point& p = aCommand.move_to.point.ConvertsToGfxPoint(); + aState.start = aState.pos = + aCommand.move_to.absolute == StyleIsAbsolute::Yes ? p + : aState.pos + p; + if (aState.ShouldUpdateLengthAndControlPoints()) { + // aState.length is unchanged, since move commands don't affect path= + // length. + aState.cp1 = aState.cp2 = aState.start; + } + break; + } + case StylePathCommand::Tag::LineTo: { + Point to = aCommand.line_to.absolute == StyleIsAbsolute::Yes + ? aCommand.line_to.point.ConvertsToGfxPoint() + : aState.pos + aCommand.line_to.point.ConvertsToGfxPoint(); + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += CalcDistanceBetweenPoints(aState.pos, to); + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; + break; + } + case StylePathCommand::Tag::CurveTo: { + const bool isRelative = aCommand.curve_to.absolute == StyleIsAbsolute::No; + Point to = isRelative + ? aState.pos + aCommand.curve_to.point.ConvertsToGfxPoint() + : aCommand.curve_to.point.ConvertsToGfxPoint(); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp1 = aCommand.curve_to.control1.ConvertsToGfxPoint(); + Point cp2 = aCommand.curve_to.control2.ConvertsToGfxPoint(); + if (isRelative) { + cp1 += aState.pos; + cp2 += aState.pos; + } + aState.length += + (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to); + aState.cp2 = cp2; + aState.cp1 = to; + } + aState.pos = to; + break; + } + case StylePathCommand::Tag::QuadBezierCurveTo: { + const bool isRelative = aCommand.curve_to.absolute == StyleIsAbsolute::No; + Point to = + isRelative + ? aState.pos + + aCommand.quad_bezier_curve_to.point.ConvertsToGfxPoint() + : aCommand.quad_bezier_curve_to.point.ConvertsToGfxPoint(); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp = + isRelative + ? aState.pos + aCommand.quad_bezier_curve_to.control1 + .ConvertsToGfxPoint() + : aCommand.quad_bezier_curve_to.control1.ConvertsToGfxPoint(); + aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to); + aState.cp1 = cp; + aState.cp2 = to; + } + aState.pos = to; + break; + } + case StylePathCommand::Tag::EllipticalArc: { + Point to = + aCommand.elliptical_arc.absolute == StyleIsAbsolute::Yes + ? aCommand.elliptical_arc.point.ConvertsToGfxPoint() + : aState.pos + aCommand.elliptical_arc.point.ConvertsToGfxPoint(); + if (aState.ShouldUpdateLengthAndControlPoints()) { + const auto& arc = aCommand.elliptical_arc; + float dist = 0; + Point radii(arc.rx, arc.ry); + if (radii.x == 0.0f || radii.y == 0.0f) { + dist = CalcDistanceBetweenPoints(aState.pos, to); + } else { + Point bez[4] = {aState.pos, Point(0, 0), Point(0, 0), Point(0, 0)}; + SVGArcConverter converter(aState.pos, to, radii, arc.angle, + arc.large_arc_flag._0, arc.sweep_flag._0); + while (converter.GetNextSegment(&bez[1], &bez[2], &bez[3])) { + dist += CalcBezLengthHelper(bez, 4, 0, SplitCubicBezier); + bez[0] = bez[3]; + } + } + aState.length += dist; + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; + break; + } + case StylePathCommand::Tag::HorizontalLineTo: { + Point to(aCommand.horizontal_line_to.absolute == StyleIsAbsolute::Yes + ? aCommand.horizontal_line_to.x + : aState.pos.x + aCommand.horizontal_line_to.x, + aState.pos.y); + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += std::fabs(to.x - aState.pos.x); + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; + break; + } + case StylePathCommand::Tag::VerticalLineTo: { + Point to(aState.pos.x, + aCommand.vertical_line_to.absolute == StyleIsAbsolute::Yes + ? aCommand.vertical_line_to.y + : aState.pos.y + aCommand.vertical_line_to.y); + if (aState.ShouldUpdateLengthAndControlPoints()) { + aState.length += std::fabs(to.y - aState.pos.y); + aState.cp1 = aState.cp2 = to; + } + aState.pos = to; + break; + } + case StylePathCommand::Tag::SmoothCurveTo: { + const bool isRelative = + aCommand.smooth_curve_to.absolute == StyleIsAbsolute::No; + Point to = + isRelative + ? aState.pos + aCommand.smooth_curve_to.point.ConvertsToGfxPoint() + : aCommand.smooth_curve_to.point.ConvertsToGfxPoint(); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp1 = aState.pos - (aState.cp2 - aState.pos); + Point cp2 = + isRelative + ? aState.pos + + aCommand.smooth_curve_to.control2.ConvertsToGfxPoint() + : aCommand.smooth_curve_to.control2.ConvertsToGfxPoint(); + aState.length += + (float)CalcLengthOfCubicBezier(aState.pos, cp1, cp2, to); + aState.cp2 = cp2; + aState.cp1 = to; + } + aState.pos = to; + break; + } + case StylePathCommand::Tag::SmoothQuadBezierCurveTo: { + Point to = aCommand.smooth_curve_to.absolute == StyleIsAbsolute::Yes + ? aCommand.smooth_curve_to.point.ConvertsToGfxPoint() + : aState.pos + + aCommand.smooth_curve_to.point.ConvertsToGfxPoint(); + if (aState.ShouldUpdateLengthAndControlPoints()) { + Point cp = aState.pos - (aState.cp1 - aState.pos); + aState.length += (float)CalcLengthOfQuadraticBezier(aState.pos, cp, to); + aState.cp1 = cp; + aState.cp2 = to; + } + aState.pos = to; + break; + } + case StylePathCommand::Tag::Unknown: + MOZ_ASSERT_UNREACHABLE("Unacceptable path segment type"); + } +} + +// Possible directions of an edge that doesn't immediately disqualify the path +// as a rectangle. +enum class EdgeDir { + LEFT, + RIGHT, + UP, + DOWN, + // NONE represents (almost) zero-length edges, they should be ignored. + NONE, +}; + +Maybe GetDirection(Point v) { + if (!std::isfinite(v.x.value) || !std::isfinite(v.y.value)) { + return Nothing(); + } + + bool x = fabs(v.x) > 0.001; + bool y = fabs(v.y) > 0.001; + if (x && y) { + return Nothing(); + } + + if (!x && !y) { + return Some(EdgeDir::NONE); + } + + if (x) { + return Some(v.x > 0.0 ? EdgeDir::RIGHT : EdgeDir::LEFT); + } + + return Some(v.y > 0.0 ? EdgeDir::DOWN : EdgeDir::UP); +} + +EdgeDir OppositeDirection(EdgeDir dir) { + switch (dir) { + case EdgeDir::LEFT: + return EdgeDir::RIGHT; + case EdgeDir::RIGHT: + return EdgeDir::LEFT; + case EdgeDir::UP: + return EdgeDir::DOWN; + case EdgeDir::DOWN: + return EdgeDir::UP; + default: + return EdgeDir::NONE; + } +} + +struct IsRectHelper { + Point min; + Point max; + EdgeDir currentDir; + // Index of the next corner. + uint32_t idx; + EdgeDir dirs[4]; + + bool Edge(Point from, Point to) { + auto edge = to - from; + + auto maybeDir = GetDirection(edge); + if (maybeDir.isNothing()) { + return false; + } + + EdgeDir dir = maybeDir.value(); + + if (dir == EdgeDir::NONE) { + // zero-length edges aren't an issue. + return true; + } + + if (dir != currentDir) { + // The edge forms a corner with the previous edge. + if (idx >= 4) { + // We are at the 5th corner, can't be a rectangle. + return false; + } + + if (dir == OppositeDirection(currentDir)) { + // Can turn left or right but not a full 180 degrees. + return false; + } + + dirs[idx] = dir; + idx += 1; + currentDir = dir; + } + + min.x = fmin(min.x, to.x); + min.y = fmin(min.y, to.y); + max.x = fmax(max.x, to.x); + max.y = fmax(max.y, to.y); + + return true; + } + + bool EndSubpath() { + if (idx != 4) { + return false; + } + + if (dirs[0] != OppositeDirection(dirs[2]) || + dirs[1] != OppositeDirection(dirs[3])) { + return false; + } + + return true; + } +}; + +bool ApproxEqual(gfx::Point a, gfx::Point b) { + auto v = b - a; + return fabs(v.x) < 0.001 && fabs(v.y) < 0.001; +} + +Maybe SVGPathToAxisAlignedRect(Span aPath) { + Point pathStart(0.0, 0.0); + Point segStart(0.0, 0.0); + IsRectHelper helper = { + Point(0.0, 0.0), + Point(0.0, 0.0), + EdgeDir::NONE, + 0, + {EdgeDir::NONE, EdgeDir::NONE, EdgeDir::NONE, EdgeDir::NONE}, + }; + + for (const StylePathCommand& cmd : aPath) { + switch (cmd.tag) { + case StylePathCommand::Tag::MoveTo: { + Point to = cmd.move_to.point.ConvertsToGfxPoint(); + if (helper.idx != 0) { + // This is overly strict since empty moveto sequences such as "M 10 12 + // M 3 2 M 0 0" render nothing, but I expect it won't make us miss a + // lot of rect-shaped paths in practice and lets us avoidhandling + // special caps for empty sub-paths like "M 0 0 L 0 0" and "M 1 2 Z". + return Nothing(); + } + + if (!ApproxEqual(pathStart, segStart)) { + // If we were only interested in filling we could auto-close here + // by calling helper.Edge like in the ClosePath case and detect some + // unclosed paths as rectangles. + // + // For example: + // - "M 1 0 L 0 0 L 0 1 L 1 1 L 1 0" are both rects for filling and + // stroking. + // - "M 1 0 L 0 0 L 0 1 L 1 1" fills a rect but the stroke is shaped + // like a C. + return Nothing(); + } + + if (helper.idx != 0 && !helper.EndSubpath()) { + return Nothing(); + } + + if (cmd.move_to.absolute == StyleIsAbsolute::No) { + to = segStart + to; + } + + pathStart = to; + segStart = to; + if (helper.idx == 0) { + helper.min = to; + helper.max = to; + } + + break; + } + case StylePathCommand::Tag::ClosePath: { + if (!helper.Edge(segStart, pathStart)) { + return Nothing(); + } + if (!helper.EndSubpath()) { + return Nothing(); + } + pathStart = segStart; + break; + } + case StylePathCommand::Tag::LineTo: { + Point to = cmd.line_to.point.ConvertsToGfxPoint(); + if (cmd.line_to.absolute == StyleIsAbsolute::No) { + to = segStart + to; + } + + if (!helper.Edge(segStart, to)) { + return Nothing(); + } + segStart = to; + break; + } + case StylePathCommand::Tag::HorizontalLineTo: { + Point to = gfx::Point(cmd.horizontal_line_to.x, segStart.y); + if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::No) { + to.x += segStart.x; + } + + if (!helper.Edge(segStart, to)) { + return Nothing(); + } + segStart = to; + break; + } + case StylePathCommand::Tag::VerticalLineTo: { + Point to = gfx::Point(segStart.x, cmd.vertical_line_to.y); + if (cmd.horizontal_line_to.absolute == StyleIsAbsolute::No) { + to.y += segStart.y; + } + + if (!helper.Edge(segStart, to)) { + return Nothing(); + } + segStart = to; + break; + } + default: + return Nothing(); + } + } + + if (!ApproxEqual(pathStart, segStart)) { + // Same situation as with moveto regarding stroking not fullly closed path + // even though the fill is a rectangle. + return Nothing(); + } + + if (!helper.EndSubpath()) { + return Nothing(); + } + + auto size = (helper.max - helper.min); + return Some(Rect(helper.min, Size(size.x, size.y))); +} + +} // namespace mozilla diff --git a/dom/svg/SVGPathSegUtils.h b/dom/svg/SVGPathSegUtils.h new file mode 100644 index 0000000000..0f16c066a9 --- /dev/null +++ b/dom/svg/SVGPathSegUtils.h @@ -0,0 +1,286 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPATHSEGUTILS_H_ +#define DOM_SVG_SVGPATHSEGUTILS_H_ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGPathSegBinding.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/Rect.h" +#include "nsDebug.h" + +namespace mozilla { + +struct StylePathCommand; + +#define NS_SVG_PATH_SEG_MAX_ARGS 7 +#define NS_SVG_PATH_SEG_FIRST_VALID_TYPE \ + dom::SVGPathSeg_Binding::PATHSEG_CLOSEPATH +#define NS_SVG_PATH_SEG_LAST_VALID_TYPE \ + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL +#define NS_SVG_PATH_SEG_TYPE_COUNT (NS_SVG_PATH_SEG_LAST_VALID_TYPE + 1) + +/** + * Code that works with path segments can use an instance of this class to + * store/provide information about the start of the current subpath and the + * last path segment (if any). + */ +struct SVGPathTraversalState { + using Point = gfx::Point; + + enum TraversalMode { eUpdateAll, eUpdateOnlyStartAndCurrentPos }; + + SVGPathTraversalState() + : start(0.0, 0.0), + pos(0.0, 0.0), + cp1(0.0, 0.0), + cp2(0.0, 0.0), + length(0.0), + mode(eUpdateAll) {} + + bool ShouldUpdateLengthAndControlPoints() { return mode == eUpdateAll; } + + Point start; // start point of current sub path (reset each moveto) + + Point pos; // current position (end point of previous segment) + + Point cp1; // quadratic control point - if the previous segment was a + // quadratic bezier curve then this is set to the absolute + // position of its control point, otherwise its set to pos + + Point cp2; // cubic control point - if the previous segment was a cubic + // bezier curve then this is set to the absolute position of + // its second control point, otherwise it's set to pos + + float length; // accumulated path length + + TraversalMode mode; // indicates what to track while traversing a path +}; + +/** + * This class is just a collection of static methods - it doesn't have any data + * members, and it's not possible to create instances of this class. This class + * exists purely as a convenient place to gather together a bunch of methods + * related to manipulating and answering questions about path segments. + * Internally we represent path segments purely as an array of floats. See the + * comment documenting SVGPathData for more info on that. + * + * The DOM wrapper classes for encoded path segments (data contained in + * instances of SVGPathData) is DOMSVGPathSeg and its sub-classes. Note that + * there are multiple different DOM classes for path segs - one for each of the + * 19 SVG 1.1 segment types. + */ +class SVGPathSegUtils { + private: + SVGPathSegUtils() = default; // private to prevent instances + + public: + static void GetValueAsString(const float* aSeg, nsAString& aValue); + + /** + * Encode a segment type enum to a float. + * + * At some point in the future we will likely want to encode other + * information into the float, such as whether the command was explicit or + * not. For now all this method does is save on int to float runtime + * conversion by requiring uint32_t and float to be of the same size so we + * can simply do a bitwise uint32_t<->float copy. + */ + static float EncodeType(uint32_t aType) { + static_assert(sizeof(uint32_t) == sizeof(float), + "sizeof uint32_t and float must be the same"); + MOZ_ASSERT(IsValidType(aType), "Seg type not recognized"); + return *(reinterpret_cast(&aType)); + } + + static uint32_t DecodeType(float aType) { + static_assert(sizeof(uint32_t) == sizeof(float), + "sizeof uint32_t and float must be the same"); + uint32_t type = *(reinterpret_cast(&aType)); + MOZ_ASSERT(IsValidType(type), "Seg type not recognized"); + return type; + } + + static char16_t GetPathSegTypeAsLetter(uint32_t aType) { + MOZ_ASSERT(IsValidType(aType), "Seg type not recognized"); + + static const char16_t table[] = { + char16_t('x'), // 0 == PATHSEG_UNKNOWN + char16_t('z'), // 1 == PATHSEG_CLOSEPATH + char16_t('M'), // 2 == PATHSEG_MOVETO_ABS + char16_t('m'), // 3 == PATHSEG_MOVETO_REL + char16_t('L'), // 4 == PATHSEG_LINETO_ABS + char16_t('l'), // 5 == PATHSEG_LINETO_REL + char16_t('C'), // 6 == PATHSEG_CURVETO_CUBIC_ABS + char16_t('c'), // 7 == PATHSEG_CURVETO_CUBIC_REL + char16_t('Q'), // 8 == PATHSEG_CURVETO_QUADRATIC_ABS + char16_t('q'), // 9 == PATHSEG_CURVETO_QUADRATIC_REL + char16_t('A'), // 10 == PATHSEG_ARC_ABS + char16_t('a'), // 11 == PATHSEG_ARC_REL + char16_t('H'), // 12 == PATHSEG_LINETO_HORIZONTAL_ABS + char16_t('h'), // 13 == PATHSEG_LINETO_HORIZONTAL_REL + char16_t('V'), // 14 == PATHSEG_LINETO_VERTICAL_ABS + char16_t('v'), // 15 == PATHSEG_LINETO_VERTICAL_REL + char16_t('S'), // 16 == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS + char16_t('s'), // 17 == PATHSEG_CURVETO_CUBIC_SMOOTH_REL + char16_t('T'), // 18 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS + char16_t('t') // 19 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL + }; + static_assert(MOZ_ARRAY_LENGTH(table) == NS_SVG_PATH_SEG_TYPE_COUNT, + "Unexpected table size"); + + return table[aType]; + } + + static uint32_t ArgCountForType(uint32_t aType) { + MOZ_ASSERT(IsValidType(aType), "Seg type not recognized"); + + static const uint8_t table[] = { + 0, // 0 == PATHSEG_UNKNOWN + 0, // 1 == PATHSEG_CLOSEPATH + 2, // 2 == PATHSEG_MOVETO_ABS + 2, // 3 == PATHSEG_MOVETO_REL + 2, // 4 == PATHSEG_LINETO_ABS + 2, // 5 == PATHSEG_LINETO_REL + 6, // 6 == PATHSEG_CURVETO_CUBIC_ABS + 6, // 7 == PATHSEG_CURVETO_CUBIC_REL + 4, // 8 == PATHSEG_CURVETO_QUADRATIC_ABS + 4, // 9 == PATHSEG_CURVETO_QUADRATIC_REL + 7, // 10 == PATHSEG_ARC_ABS + 7, // 11 == PATHSEG_ARC_REL + 1, // 12 == PATHSEG_LINETO_HORIZONTAL_ABS + 1, // 13 == PATHSEG_LINETO_HORIZONTAL_REL + 1, // 14 == PATHSEG_LINETO_VERTICAL_ABS + 1, // 15 == PATHSEG_LINETO_VERTICAL_REL + 4, // 16 == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS + 4, // 17 == PATHSEG_CURVETO_CUBIC_SMOOTH_REL + 2, // 18 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS + 2 // 19 == PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL + }; + static_assert(MOZ_ARRAY_LENGTH(table) == NS_SVG_PATH_SEG_TYPE_COUNT, + "Unexpected table size"); + + return table[aType]; + } + + /** + * Convenience so that callers can pass a float containing an encoded type + * and have it decoded implicitly. + */ + static uint32_t ArgCountForType(float aType) { + return ArgCountForType(DecodeType(aType)); + } + + static bool IsValidType(uint32_t aType) { + return aType >= NS_SVG_PATH_SEG_FIRST_VALID_TYPE && + aType <= NS_SVG_PATH_SEG_LAST_VALID_TYPE; + } + + static bool IsCubicType(uint32_t aType) { + return aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_CUBIC_REL || + aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_CUBIC_ABS || + aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_CUBIC_SMOOTH_REL || + aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_CUBIC_SMOOTH_ABS; + } + + static bool IsQuadraticType(uint32_t aType) { + return aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_REL || + aType == dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_ABS || + aType == + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL || + aType == + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS; + } + + static bool IsArcType(uint32_t aType) { + return aType == dom::SVGPathSeg_Binding::PATHSEG_ARC_ABS || + aType == dom::SVGPathSeg_Binding::PATHSEG_ARC_REL; + } + + static bool IsRelativeOrAbsoluteType(uint32_t aType) { + MOZ_ASSERT(IsValidType(aType), "Seg type not recognized"); + + // When adding a new path segment type, ensure that the returned condition + // below is still correct. + static_assert( + NS_SVG_PATH_SEG_LAST_VALID_TYPE == + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, + "Unexpected type"); + + return aType >= dom::SVGPathSeg_Binding::PATHSEG_MOVETO_ABS; + } + + static bool IsRelativeType(uint32_t aType) { + MOZ_ASSERT(IsRelativeOrAbsoluteType(aType), + "IsRelativeType called with segment type that does not come in " + "relative and absolute forms"); + + // When adding a new path segment type, ensure that the returned condition + // below is still correct. + static_assert( + NS_SVG_PATH_SEG_LAST_VALID_TYPE == + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, + "Unexpected type"); + + return aType & 1; + } + + static uint32_t RelativeVersionOfType(uint32_t aType) { + MOZ_ASSERT(IsRelativeOrAbsoluteType(aType), + "RelativeVersionOfType called with segment type that does not " + "come in relative and absolute forms"); + + // When adding a new path segment type, ensure that the returned condition + // below is still correct. + static_assert( + NS_SVG_PATH_SEG_LAST_VALID_TYPE == + dom::SVGPathSeg_Binding::PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, + "Unexpected type"); + + return aType | 1; + } + + static uint32_t SameTypeModuloRelativeness(uint32_t aType1, uint32_t aType2) { + if (!IsRelativeOrAbsoluteType(aType1)) { + return aType1 == aType2; + } + + return RelativeVersionOfType(aType1) == RelativeVersionOfType(aType2); + } + + /** + * Traverse the given path segment and update the SVGPathTraversalState + * object. + */ + static void TraversePathSegment(const float* aData, + SVGPathTraversalState& aState); + + /** + * Traverse the given path segment and update the SVGPathTraversalState + * object. This is identical to the above one but accepts StylePathCommand. + */ + static void TraversePathSegment(const StylePathCommand& aCommand, + SVGPathTraversalState& aState); +}; + +/// Detect whether the path represents a rectangle (for both filling AND +/// stroking) and if so returns it. +/// +/// This is typically useful for google slides which has many of these rectangle +/// shaped paths. It handles the same scenarios as skia's +/// SkPathPriv::IsRectContour which it is inspried from, including zero-length +/// edges and multiple points on edges of the rectangle, and doesn't attempt to +/// detect flat curves (that could easily be added but the expectation is that +/// since skia doesn't fast path it we're not likely to run into it in +/// practice). +/// +/// We could implement something similar for polygons. +Maybe SVGPathToAxisAlignedRect(Span aPath); + +} // namespace mozilla + +#endif // DOM_SVG_SVGPATHSEGUTILS_H_ diff --git a/dom/svg/SVGPatternElement.cpp b/dom/svg/SVGPatternElement.cpp new file mode 100644 index 0000000000..5a0a9a46f2 --- /dev/null +++ b/dom/svg/SVGPatternElement.cpp @@ -0,0 +1,157 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGPatternElement.h" + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGPatternElementBinding.h" +#include "mozilla/dom/SVGUnitTypesBinding.h" +#include "DOMSVGAnimatedTransformList.h" +#include "nsGkAtoms.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Pattern) + +namespace mozilla::dom { + +using namespace SVGUnitTypes_Binding; + +JSObject* SVGPatternElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGPatternElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//--------------------- Patterns ------------------------ + +SVGElement::LengthInfo SVGPatternElement::sLengthInfo[4] = { + {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, + {nsGkAtoms::width, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::X}, + {nsGkAtoms::height, 0, SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE, + SVGContentUtils::Y}, +}; + +SVGElement::EnumInfo SVGPatternElement::sEnumInfo[2] = { + {nsGkAtoms::patternUnits, sSVGUnitTypesMap, + SVG_UNIT_TYPE_OBJECTBOUNDINGBOX}, + {nsGkAtoms::patternContentUnits, sSVGUnitTypesMap, + SVG_UNIT_TYPE_USERSPACEONUSE}}; + +SVGElement::StringInfo SVGPatternElement::sStringInfo[2] = { + {nsGkAtoms::href, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_XLink, true}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGPatternElement::SVGPatternElement( + already_AddRefed&& aNodeInfo) + : SVGPatternElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode method + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGPatternElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGPatternElement::ViewBox() { + return mViewBox.ToSVGAnimatedRect(this); +} + +already_AddRefed +SVGPatternElement::PreserveAspectRatio() { + return mPreserveAspectRatio.ToDOMAnimatedPreserveAspectRatio(this); +} + +//---------------------------------------------------------------------- + +already_AddRefed SVGPatternElement::PatternUnits() { + return mEnumAttributes[PATTERNUNITS].ToDOMAnimatedEnum(this); +} + +already_AddRefed +SVGPatternElement::PatternContentUnits() { + return mEnumAttributes[PATTERNCONTENTUNITS].ToDOMAnimatedEnum(this); +} + +already_AddRefed +SVGPatternElement::PatternTransform() { + // We're creating a DOM wrapper, so we must tell GetAnimatedTransformList + // to allocate the DOMSVGAnimatedTransformList if it hasn't already done so: + return DOMSVGAnimatedTransformList::GetDOMWrapper( + GetAnimatedTransformList(DO_ALLOCATE), this); +} + +already_AddRefed SVGPatternElement::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGPatternElement::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGPatternElement::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGPatternElement::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGPatternElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGAnimatedTransformList* SVGPatternElement::GetAnimatedTransformList( + uint32_t aFlags) { + if (!mPatternTransform && (aFlags & DO_ALLOCATE)) { + mPatternTransform = MakeUnique(); + } + return mPatternTransform.get(); +} + +/* virtual */ +bool SVGPatternElement::HasValidDimensions() const { + return mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() && + mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0 && + mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() && + mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0; +} + +SVGElement::LengthAttributesInfo SVGPatternElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +SVGElement::EnumAttributesInfo SVGPatternElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGAnimatedViewBox* SVGPatternElement::GetAnimatedViewBox() { + return &mViewBox; +} + +SVGAnimatedPreserveAspectRatio* +SVGPatternElement::GetAnimatedPreserveAspectRatio() { + return &mPreserveAspectRatio; +} + +SVGElement::StringAttributesInfo SVGPatternElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGPatternElement.h b/dom/svg/SVGPatternElement.h new file mode 100644 index 0000000000..5df515d274 --- /dev/null +++ b/dom/svg/SVGPatternElement.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPATTERNELEMENT_H_ +#define DOM_SVG_SVGPATTERNELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedPreserveAspectRatio.h" +#include "SVGAnimatedString.h" +#include "SVGAnimatedTransformList.h" +#include "SVGAnimatedViewBox.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/UniquePtr.h" + +nsresult NS_NewSVGPatternElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla { +class SVGPatternFrame; + +namespace dom { +class DOMSVGAnimatedTransformList; + +using SVGPatternElementBase = SVGElement; + +class SVGPatternElement final : public SVGPatternElementBase { + friend class mozilla::SVGPatternFrame; + + protected: + friend nsresult(::NS_NewSVGPatternElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGPatternElement( + already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + + public: + // nsIContent interface + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + virtual mozilla::SVGAnimatedTransformList* GetAnimatedTransformList( + uint32_t aFlags = 0) override; + nsStaticAtom* GetTransformListAttrName() const override { + return nsGkAtoms::patternTransform; + } + + // WebIDL + already_AddRefed ViewBox(); + already_AddRefed PreserveAspectRatio(); + already_AddRefed PatternUnits(); + already_AddRefed PatternContentUnits(); + already_AddRefed PatternTransform(); + already_AddRefed X(); + already_AddRefed Y(); + already_AddRefed Width(); + already_AddRefed Height(); + already_AddRefed Href(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + virtual SVGAnimatedPreserveAspectRatio* GetAnimatedPreserveAspectRatio() + override; + SVGAnimatedViewBox* GetAnimatedViewBox() override; + + enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT }; + SVGAnimatedLength mLengthAttributes[4]; + static LengthInfo sLengthInfo[4]; + + enum { PATTERNUNITS, PATTERNCONTENTUNITS }; + SVGAnimatedEnumeration mEnumAttributes[2]; + static EnumInfo sEnumInfo[2]; + + UniquePtr mPatternTransform; + + enum { HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; + + // SVGFitToViewbox properties + SVGAnimatedViewBox mViewBox; + SVGAnimatedPreserveAspectRatio mPreserveAspectRatio; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGPATTERNELEMENT_H_ diff --git a/dom/svg/SVGPoint.h b/dom/svg/SVGPoint.h new file mode 100644 index 0000000000..2dcee49cac --- /dev/null +++ b/dom/svg/SVGPoint.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPOINT_H_ +#define DOM_SVG_SVGPOINT_H_ + +#include "nsDebug.h" +#include "gfxPoint.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/FloatingPoint.h" + +namespace mozilla { + +/** + * This class is currently used for point list attributes. + * + * The DOM wrapper class for this class is DOMSVGPoint. + */ +class SVGPoint { + using Point = mozilla::gfx::Point; + + public: + SVGPoint() : mX(0.0f), mY(0.0f) {} + + SVGPoint(float aX, float aY) : mX(aX), mY(aY) { + NS_ASSERTION(IsValid(), "Constructed an invalid SVGPoint"); + } + + bool operator==(const SVGPoint& rhs) const { + return mX == rhs.mX && mY == rhs.mY; + } + + SVGPoint& operator+=(const SVGPoint& rhs) { + mX += rhs.mX; + mY += rhs.mY; + return *this; + } + + operator gfxPoint() const { return gfxPoint(mX, mY); } + + operator Point() const { return Point(mX, mY); } + +#ifdef DEBUG + bool IsValid() const { return std::isfinite(mX) && std::isfinite(mY); } +#endif + + void SetX(float aX) { mX = aX; } + void SetY(float aY) { mY = aY; } + float GetX() const { return mX; } + float GetY() const { return mY; } + + bool operator!=(const SVGPoint& rhs) const { + return mX != rhs.mX || mY != rhs.mY; + } + + float mX; + float mY; +}; + +inline SVGPoint operator+(const SVGPoint& aP1, const SVGPoint& aP2) { + return SVGPoint(aP1.mX + aP2.mX, aP1.mY + aP2.mY); +} + +inline SVGPoint operator-(const SVGPoint& aP1, const SVGPoint& aP2) { + return SVGPoint(aP1.mX - aP2.mX, aP1.mY - aP2.mY); +} + +inline SVGPoint operator*(float aFactor, const SVGPoint& aPoint) { + return SVGPoint(aFactor * aPoint.mX, aFactor * aPoint.mY); +} + +inline SVGPoint operator*(const SVGPoint& aPoint, float aFactor) { + return SVGPoint(aFactor * aPoint.mX, aFactor * aPoint.mY); +} + +} // namespace mozilla + +#endif // DOM_SVG_SVGPOINT_H_ diff --git a/dom/svg/SVGPointList.cpp b/dom/svg/SVGPointList.cpp new file mode 100644 index 0000000000..1a7ccf908f --- /dev/null +++ b/dom/svg/SVGPointList.cpp @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" + +#include "SVGPointList.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" +#include "nsTextFormatter.h" +#include "SVGContentUtils.h" + +namespace mozilla { + +nsresult SVGPointList::CopyFrom(const SVGPointList& rhs) { + if (!mItems.Assign(rhs.mItems, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +void SVGPointList::GetValueAsString(nsAString& aValue) const { + aValue.Truncate(); + char16_t buf[50]; + uint32_t last = mItems.Length() - 1; + for (uint32_t i = 0; i < mItems.Length(); ++i) { + // Would like to use aValue.AppendPrintf("%f,%f", item.mX, item.mY), + // but it's not possible to always avoid trailing zeros. + nsTextFormatter::snprintf(buf, ArrayLength(buf), u"%g,%g", + double(mItems[i].mX), double(mItems[i].mY)); + // We ignore OOM, since it's not useful for us to return an error. + aValue.Append(buf); + if (i != last) { + aValue.Append(' '); + } + } +} + +nsresult SVGPointList::SetValueFromString(const nsAString& aValue) { + // The spec says that the list is parsed and accepted up to the first error + // encountered, so we must call CopyFrom even if an error occurs. We still + // want to throw any error code from setAttribute if there's a problem + // though, so we must take care to return any error code. + + nsresult rv = NS_OK; + + SVGPointList temp; + + nsCharSeparatedTokenizerTemplate + tokenizer(aValue, ','); + + while (tokenizer.hasMoreTokens()) { + const nsAString& token = tokenizer.nextToken(); + + RangedPtr iter = SVGContentUtils::GetStartRangedPtr(token); + const RangedPtr end = + SVGContentUtils::GetEndRangedPtr(token); + + float x; + if (!SVGContentUtils::ParseNumber(iter, end, x)) { + rv = NS_ERROR_DOM_SYNTAX_ERR; + break; + } + + float y; + if (iter == end) { + if (!tokenizer.hasMoreTokens() || + !SVGContentUtils::ParseNumber(tokenizer.nextToken(), y)) { + rv = NS_ERROR_DOM_SYNTAX_ERR; + break; + } + } else { + // It's possible for the token to be 10-30 which has + // no separator but needs to be parsed as 10, -30 + const nsAString& leftOver = Substring(iter.get(), end.get()); + if (leftOver[0] != '-' || !SVGContentUtils::ParseNumber(leftOver, y)) { + rv = NS_ERROR_DOM_SYNTAX_ERR; + break; + } + } + temp.AppendItem(SVGPoint(x, y)); + } + if (tokenizer.separatorAfterCurrentToken()) { + rv = NS_ERROR_DOM_SYNTAX_ERR; // trailing comma + } + nsresult rv2 = CopyFrom(temp); + if (NS_FAILED(rv2)) { + return rv2; // prioritize OOM error code over syntax errors + } + return rv; +} + +} // namespace mozilla diff --git a/dom/svg/SVGPointList.h b/dom/svg/SVGPointList.h new file mode 100644 index 0000000000..959dbc95e4 --- /dev/null +++ b/dom/svg/SVGPointList.h @@ -0,0 +1,207 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPOINTLIST_H_ +#define DOM_SVG_SVGPOINTLIST_H_ + +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsIContent.h" +#include "nsINode.h" +#include "nsIWeakReferenceUtils.h" +#include "SVGElement.h" +#include "nsTArray.h" +#include "SVGPoint.h" + +#include + +namespace mozilla { + +namespace dom { +class DOMSVGPoint; +class DOMSVGPointList; +} // namespace dom + +/** + * ATTENTION! WARNING! WATCH OUT!! + * + * Consumers that modify objects of this type absolutely MUST keep the DOM + * wrappers for those lists (if any) in sync!! That's why this class is so + * locked down. + * + * The DOM wrapper class for this class is DOMSVGPointList. + */ +class SVGPointList { + friend class SVGAnimatedPointList; + friend class dom::DOMSVGPointList; + friend class dom::DOMSVGPoint; + + public: + SVGPointList() = default; + ~SVGPointList() = default; + + // Only methods that don't make/permit modification to this list are public. + // Only our friend classes can access methods that may change us. + + /// This may return an incomplete string on OOM, but that's acceptable. + void GetValueAsString(nsAString& aValue) const; + + bool IsEmpty() const { return mItems.IsEmpty(); } + + uint32_t Length() const { return mItems.Length(); } + + const SVGPoint& operator[](uint32_t aIndex) const { return mItems[aIndex]; } + + bool operator==(const SVGPointList& rhs) const { + // memcmp can be faster than |mItems == rhs.mItems| + return mItems.Length() == rhs.mItems.Length() && + memcmp(mItems.Elements(), rhs.mItems.Elements(), + mItems.Length() * sizeof(SVGPoint)) == 0; + } + + bool SetCapacity(uint32_t aSize) { + return mItems.SetCapacity(aSize, fallible); + } + + void Compact() { mItems.Compact(); } + + // Access to methods that can modify objects of this type is deliberately + // limited. This is to reduce the chances of someone modifying objects of + // this type without taking the necessary steps to keep DOM wrappers in sync. + // If you need wider access to these methods, consider adding a method to + // SVGAnimatedPointList and having that class act as an intermediary so it + // can take care of keeping DOM wrappers in sync. + + protected: + /** + * This may fail on OOM if the internal capacity needs to be increased, in + * which case the list will be left unmodified. + */ + nsresult CopyFrom(const SVGPointList& rhs); + void SwapWith(SVGPointList& aRhs) { mItems.SwapElements(aRhs.mItems); } + + SVGPoint& operator[](uint32_t aIndex) { return mItems[aIndex]; } + + /** + * This may fail (return false) on OOM if the internal capacity is being + * increased, in which case the list will be left unmodified. + */ + bool SetLength(uint32_t aNumberOfItems) { + return mItems.SetLength(aNumberOfItems, fallible); + } + + private: + // Marking the following private only serves to show which methods are only + // used by our friend classes (as opposed to our subclasses) - it doesn't + // really provide additional safety. + + nsresult SetValueFromString(const nsAString& aValue); + + void Clear() { mItems.Clear(); } + + bool InsertItem(uint32_t aIndex, const SVGPoint& aPoint) { + if (aIndex >= mItems.Length()) { + aIndex = mItems.Length(); + } + return !!mItems.InsertElementAt(aIndex, aPoint, fallible); + } + + void ReplaceItem(uint32_t aIndex, const SVGPoint& aPoint) { + MOZ_ASSERT(aIndex < mItems.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mItems[aIndex] = aPoint; + } + + void RemoveItem(uint32_t aIndex) { + MOZ_ASSERT(aIndex < mItems.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mItems.RemoveElementAt(aIndex); + } + + bool AppendItem(SVGPoint aPoint) { + return !!mItems.AppendElement(aPoint, fallible); + } + + protected: + /* See SVGLengthList for the rationale for using FallibleTArray + * instead of FallibleTArray. + */ + FallibleTArray mItems; +}; + +/** + * This SVGPointList subclass is for SVGPointListSMILType which needs a + * mutable version of SVGPointList. Instances of this class do not have + * DOM wrappers that need to be kept in sync, so we can safely expose any + * protected base class methods required by the SMIL code. + * + * This class contains a strong reference to the element that instances of + * this class are being used to animate. This is because the SMIL code stores + * instances of this class in SMILValue objects, some of which are cached. + * Holding a strong reference to the element here prevents the element from + * disappearing out from under the SMIL code unexpectedly. + */ +class SVGPointListAndInfo : public SVGPointList { + public: + explicit SVGPointListAndInfo(dom::SVGElement* aElement = nullptr) + : mElement(do_GetWeakReference(static_cast(aElement))) {} + + void SetInfo(dom::SVGElement* aElement) { + mElement = do_GetWeakReference(static_cast(aElement)); + } + + dom::SVGElement* Element() const { + nsCOMPtr e = do_QueryReferent(mElement); + return static_cast(e.get()); + } + + /** + * Returns true if this object is an "identity" value, from the perspective + * of SMIL. In other words, returns true until the initial value set up in + * SVGPointListSMILType::Init() has been changed with a SetInfo() call. + */ + bool IsIdentity() const { + if (!mElement) { + MOZ_ASSERT(IsEmpty(), "target element propagation failure"); + return true; + } + return false; + } + + nsresult CopyFrom(const SVGPointListAndInfo& rhs) { + mElement = rhs.mElement; + return SVGPointList::CopyFrom(rhs); + } + + /** + * Exposed so that SVGPointList baseVals can be copied to + * SVGPointListAndInfo objects. Note that callers should also call + * SetElement() when using this method! + */ + nsresult CopyFrom(const SVGPointList& rhs) { + return SVGPointList::CopyFrom(rhs); + } + const SVGPoint& operator[](uint32_t aIndex) const { + return SVGPointList::operator[](aIndex); + } + SVGPoint& operator[](uint32_t aIndex) { + return SVGPointList::operator[](aIndex); + } + bool SetLength(uint32_t aNumberOfItems) { + return SVGPointList::SetLength(aNumberOfItems); + } + + private: + // We must keep a weak reference to our element because we may belong to a + // cached baseVal SMILValue. See the comments starting at: + // https://bugzilla.mozilla.org/show_bug.cgi?id=515116#c15 + // See also https://bugzilla.mozilla.org/show_bug.cgi?id=653497 + nsWeakPtr mElement; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGPOINTLIST_H_ diff --git a/dom/svg/SVGPointListSMILType.cpp b/dom/svg/SVGPointListSMILType.cpp new file mode 100644 index 0000000000..8de84ac3b5 --- /dev/null +++ b/dom/svg/SVGPointListSMILType.cpp @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGPointListSMILType.h" + +#include "mozilla/FloatingPoint.h" +#include "mozilla/SMILValue.h" +#include "nsMathUtils.h" +#include "SVGPointList.h" +#include + +namespace mozilla { + +/*static*/ +SVGPointListSMILType SVGPointListSMILType::sSingleton; + +//---------------------------------------------------------------------- +// nsISMILType implementation + +void SVGPointListSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + + SVGPointListAndInfo* pointList = new SVGPointListAndInfo(); + + aValue.mU.mPtr = pointList; + aValue.mType = this; +} + +void SVGPointListSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value type"); + delete static_cast(aValue.mU.mPtr); + aValue.mU.mPtr = nullptr; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGPointListSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value"); + + const SVGPointListAndInfo* src = + static_cast(aSrc.mU.mPtr); + SVGPointListAndInfo* dest = static_cast(aDest.mU.mPtr); + + return dest->CopyFrom(*src); +} + +bool SVGPointListSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected type for SMIL value"); + + return *static_cast(aLeft.mU.mPtr) == + *static_cast(aRight.mU.mPtr); +} + +nsresult SVGPointListSMILType::Add(SMILValue& aDest, + const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aValueToAdd.mType == this, "Incompatible SMIL type"); + + SVGPointListAndInfo& dest = *static_cast(aDest.mU.mPtr); + const SVGPointListAndInfo& valueToAdd = + *static_cast(aValueToAdd.mU.mPtr); + + MOZ_ASSERT(dest.Element() || valueToAdd.Element(), + "Target element propagation failure"); + + if (valueToAdd.IsIdentity()) { + return NS_OK; + } + if (dest.IsIdentity()) { + if (!dest.SetLength(valueToAdd.Length())) { + return NS_ERROR_OUT_OF_MEMORY; + } + for (uint32_t i = 0; i < dest.Length(); ++i) { + dest[i] = aCount * valueToAdd[i]; + } + dest.SetInfo(valueToAdd.Element()); // propagate target element info! + return NS_OK; + } + MOZ_ASSERT(dest.Element() == valueToAdd.Element(), + "adding values from different elements...?"); + if (dest.Length() != valueToAdd.Length()) { + // For now we only support animation between lists with the same number of + // items. SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + for (uint32_t i = 0; i < dest.Length(); ++i) { + dest[i] += aCount * valueToAdd[i]; + } + dest.SetInfo(valueToAdd.Element()); // propagate target element info! + return NS_OK; +} + +nsresult SVGPointListSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aTo.mType == this, "Incompatible SMIL type"); + + const SVGPointListAndInfo& from = + *static_cast(aFrom.mU.mPtr); + const SVGPointListAndInfo& to = + *static_cast(aTo.mU.mPtr); + + if (from.Length() != to.Length()) { + // Lists in the 'values' attribute must have the same length. + // SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + + // We return the root of the sum of the squares of the distances between the + // points at each corresponding index. + + double total = 0.0; + + for (uint32_t i = 0; i < to.Length(); ++i) { + double dx = to[i].mX - from[i].mX; + double dy = to[i].mY - from[i].mY; + total += dx * dx + dy * dy; + } + double distance = sqrt(total); + if (!std::isfinite(distance)) { + return NS_ERROR_FAILURE; + } + aDistance = distance; + + return NS_OK; +} + +nsresult SVGPointListSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Trying to interpolate different types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected types for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + + const SVGPointListAndInfo& start = + *static_cast(aStartVal.mU.mPtr); + const SVGPointListAndInfo& end = + *static_cast(aEndVal.mU.mPtr); + SVGPointListAndInfo& result = + *static_cast(aResult.mU.mPtr); + + MOZ_ASSERT(end.Element(), "Can't propagate target element"); + MOZ_ASSERT(start.Element() == end.Element() || !start.Element(), + "Different target elements"); + + if (start.Element() && // 'start' is not an "identity" value + start.Length() != end.Length()) { + // For now we only support animation between lists of the same length. + // SVGContentUtils::ReportToConsole + return NS_ERROR_FAILURE; + } + if (!result.SetLength(end.Length())) { + return NS_ERROR_OUT_OF_MEMORY; + } + + result.SetInfo(end.Element()); // propagate target element info! + + if (start.Length() != end.Length()) { + MOZ_ASSERT(start.Length() == 0, "Not an identity value"); + for (uint32_t i = 0; i < end.Length(); ++i) { + result[i] = aUnitDistance * end[i]; + } + return NS_OK; + } + for (uint32_t i = 0; i < end.Length(); ++i) { + result[i] = start[i] + (end[i] - start[i]) * aUnitDistance; + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/svg/SVGPointListSMILType.h b/dom/svg/SVGPointListSMILType.h new file mode 100644 index 0000000000..1432e5286b --- /dev/null +++ b/dom/svg/SVGPointListSMILType.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPOINTLISTSMILTYPE_H_ +#define DOM_SVG_SVGPOINTLISTSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" + +namespace mozilla { + +class SMILValue; + +//////////////////////////////////////////////////////////////////////// +// SVGPointListSMILType +// +// Operations for animating an SVGPointList. +// +class SVGPointListSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGPointListSMILType sSingleton; + + protected: + // SMILType Methods + // ------------------- + + void Init(SMILValue& aValue) const override; + + void Destroy(SMILValue& aValue) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGPointListSMILType() = default; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGPOINTLISTSMILTYPE_H_ diff --git a/dom/svg/SVGPolyElement.cpp b/dom/svg/SVGPolyElement.cpp new file mode 100644 index 0000000000..23d5d5a2f8 --- /dev/null +++ b/dom/svg/SVGPolyElement.cpp @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGPolyElement.h" +#include "DOMSVGPointList.h" +#include "mozilla/gfx/2D.h" +#include "SVGContentUtils.h" + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +//---------------------------------------------------------------------- +// Implementation + +SVGPolyElement::SVGPolyElement( + already_AddRefed&& aNodeInfo) + : SVGPolyElementBase(std::move(aNodeInfo)) {} + +already_AddRefed SVGPolyElement::Points() { + void* key = mPoints.GetBaseValKey(); + RefPtr points = + DOMSVGPointList::GetDOMWrapper(key, this, false); + return points.forget(); +} + +already_AddRefed SVGPolyElement::AnimatedPoints() { + void* key = mPoints.GetAnimValKey(); + RefPtr points = + DOMSVGPointList::GetDOMWrapper(key, this, true); + return points.forget(); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +bool SVGPolyElement::HasValidDimensions() const { + return !mPoints.GetAnimValue().IsEmpty(); +} + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +bool SVGPolyElement::AttributeDefinesGeometry(const nsAtom* aName) { + return aName == nsGkAtoms::points; +} + +void SVGPolyElement::GetMarkPoints(nsTArray* aMarks) { + const SVGPointList& points = mPoints.GetAnimValue(); + + if (!points.Length()) return; + + float px = points[0].mX, py = points[0].mY, prevAngle = 0.0; + + aMarks->AppendElement(SVGMark(px, py, 0, SVGMark::eStart)); + + for (uint32_t i = 1; i < points.Length(); ++i) { + float x = points[i].mX; + float y = points[i].mY; + float angle = std::atan2(y - py, x - px); + + // Vertex marker. + if (i == 1) { + aMarks->ElementAt(0).angle = angle; + } else { + aMarks->ElementAt(aMarks->Length() - 1).angle = + SVGContentUtils::AngleBisect(prevAngle, angle); + } + + aMarks->AppendElement(SVGMark(x, y, 0, SVGMark::eMid)); + + prevAngle = angle; + px = x; + py = y; + } + + aMarks->LastElement().angle = prevAngle; + aMarks->LastElement().type = SVGMark::eEnd; +} + +bool SVGPolyElement::GetGeometryBounds(Rect* aBounds, + const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace) { + const SVGPointList& points = mPoints.GetAnimValue(); + + if (!points.Length()) { + // Rendering of the element is disabled + aBounds->SetEmpty(); + return true; + } + + if (aStrokeOptions.mLineWidth > 0 || aToNonScalingStrokeSpace) { + // We don't handle non-scaling-stroke or stroke-miterlimit etc. yet + return false; + } + + if (aToBoundsSpace.IsRectilinear()) { + // We can avoid transforming each point and just transform the result. + // Important for large point lists. + Rect bounds(points[0], Size()); + for (uint32_t i = 1; i < points.Length(); ++i) { + bounds.ExpandToEnclose(points[i]); + } + *aBounds = aToBoundsSpace.TransformBounds(bounds); + } else { + *aBounds = Rect(aToBoundsSpace.TransformPoint(points[0]), Size()); + for (uint32_t i = 1; i < points.Length(); ++i) { + aBounds->ExpandToEnclose(aToBoundsSpace.TransformPoint(points[i])); + } + } + return true; +} +} // namespace mozilla::dom diff --git a/dom/svg/SVGPolyElement.h b/dom/svg/SVGPolyElement.h new file mode 100644 index 0000000000..12efcddd4c --- /dev/null +++ b/dom/svg/SVGPolyElement.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPOLYELEMENT_H_ +#define DOM_SVG_SVGPOLYELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "SVGAnimatedPointList.h" +#include "SVGGeometryElement.h" + +namespace mozilla::dom { + +class DOMSVGPointList; + +using SVGPolyElementBase = SVGGeometryElement; + +class SVGPolyElement : public SVGPolyElementBase { + protected: + explicit SVGPolyElement(already_AddRefed&& aNodeInfo); + + virtual ~SVGPolyElement() = default; + + public: + // interfaces + + NS_INLINE_DECL_REFCOUNTING_INHERITED(SVGPolyElement, SVGPolyElementBase) + + SVGAnimatedPointList* GetAnimatedPointList() override { return &mPoints; } + nsStaticAtom* GetPointListAttrName() const override { + return nsGkAtoms::points; + } + + // SVGElement methods: + bool HasValidDimensions() const override; + + // SVGGeometryElement methods: + bool AttributeDefinesGeometry(const nsAtom* aName) override; + bool IsMarkable() override { return true; } + void GetMarkPoints(nsTArray* aMarks) override; + bool GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; + + // WebIDL + already_AddRefed Points(); + already_AddRefed AnimatedPoints(); + + protected: + SVGAnimatedPointList mPoints; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGPOLYELEMENT_H_ diff --git a/dom/svg/SVGPolygonElement.cpp b/dom/svg/SVGPolygonElement.cpp new file mode 100644 index 0000000000..def8015153 --- /dev/null +++ b/dom/svg/SVGPolygonElement.cpp @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGPolygonElement.h" +#include "mozilla/dom/SVGPolygonElementBinding.h" +#include "mozilla/gfx/2D.h" +#include "SVGContentUtils.h" + +using namespace mozilla::gfx; + +NS_IMPL_NS_NEW_SVG_ELEMENT(Polygon) + +namespace mozilla::dom { + +JSObject* SVGPolygonElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGPolygonElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGPolygonElement::SVGPolygonElement( + already_AddRefed&& aNodeInfo) + : SVGPolygonElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGPolygonElement) + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +void SVGPolygonElement::GetMarkPoints(nsTArray* aMarks) { + SVGPolyElement::GetMarkPoints(aMarks); + + if (aMarks->IsEmpty() || aMarks->LastElement().type != SVGMark::eEnd) { + return; + } + + SVGMark* endMark = &aMarks->LastElement(); + SVGMark* startMark = &aMarks->ElementAt(0); + float angle = + std::atan2(startMark->y - endMark->y, startMark->x - endMark->x); + + endMark->type = SVGMark::eMid; + endMark->angle = SVGContentUtils::AngleBisect(angle, endMark->angle); + startMark->angle = SVGContentUtils::AngleBisect(angle, startMark->angle); + // for a polygon (as opposed to a polyline) there's an implicit extra point + // co-located with the start point that SVGPolyElement::GetMarkPoints + // doesn't return + aMarks->AppendElement( + SVGMark(startMark->x, startMark->y, startMark->angle, SVGMark::eEnd)); +} + +already_AddRefed SVGPolygonElement::BuildPath(PathBuilder* aBuilder) { + const SVGPointList& points = mPoints.GetAnimValue(); + + if (points.IsEmpty()) { + return nullptr; + } + + aBuilder->MoveTo(points[0]); + for (uint32_t i = 1; i < points.Length(); ++i) { + aBuilder->LineTo(points[i]); + } + + aBuilder->Close(); + + return aBuilder->Finish(); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGPolygonElement.h b/dom/svg/SVGPolygonElement.h new file mode 100644 index 0000000000..5916b3377d --- /dev/null +++ b/dom/svg/SVGPolygonElement.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPOLYGONELEMENT_H_ +#define DOM_SVG_SVGPOLYGONELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "SVGPolyElement.h" + +nsresult NS_NewSVGPolygonElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGPolygonElementBase = SVGPolyElement; + +class SVGPolygonElement final : public SVGPolygonElementBase { + protected: + explicit SVGPolygonElement( + already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + friend nsresult(::NS_NewSVGPolygonElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + public: + // SVGGeometryElement methods: + void GetMarkPoints(nsTArray* aMarks) override; + already_AddRefed BuildPath(PathBuilder* aBuilder) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGPOLYGONELEMENT_H_ diff --git a/dom/svg/SVGPolylineElement.cpp b/dom/svg/SVGPolylineElement.cpp new file mode 100644 index 0000000000..a657b0cefd --- /dev/null +++ b/dom/svg/SVGPolylineElement.cpp @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGPolylineElement.h" +#include "mozilla/dom/SVGPolylineElementBinding.h" +#include "mozilla/gfx/2D.h" + +using namespace mozilla::gfx; + +NS_IMPL_NS_NEW_SVG_ELEMENT(Polyline) + +namespace mozilla::dom { + +JSObject* SVGPolylineElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGPolylineElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGPolylineElement::SVGPolylineElement( + already_AddRefed&& aNodeInfo) + : SVGPolylineElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGPolylineElement) + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +already_AddRefed SVGPolylineElement::BuildPath(PathBuilder* aBuilder) { + const SVGPointList& points = mPoints.GetAnimValue(); + + if (points.IsEmpty()) { + return nullptr; + } + + aBuilder->MoveTo(points[0]); + for (uint32_t i = 1; i < points.Length(); ++i) { + aBuilder->LineTo(points[i]); + } + + return aBuilder->Finish(); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGPolylineElement.h b/dom/svg/SVGPolylineElement.h new file mode 100644 index 0000000000..2b35354a0e --- /dev/null +++ b/dom/svg/SVGPolylineElement.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPOLYLINEELEMENT_H_ +#define DOM_SVG_SVGPOLYLINEELEMENT_H_ + +#include "SVGPolyElement.h" + +nsresult NS_NewSVGPolylineElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGPolylineElementBase = SVGPolyElement; + +class SVGPolylineElement final : public SVGPolylineElementBase { + protected: + explicit SVGPolylineElement( + already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + friend nsresult(::NS_NewSVGPolylineElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + // SVGGeometryElement methods: + already_AddRefed BuildPath(PathBuilder* aBuilder) override; + + public: + // nsIContent interface + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGPOLYLINEELEMENT_H_ diff --git a/dom/svg/SVGPreserveAspectRatio.cpp b/dom/svg/SVGPreserveAspectRatio.cpp new file mode 100644 index 0000000000..0cd947e867 --- /dev/null +++ b/dom/svg/SVGPreserveAspectRatio.cpp @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGPreserveAspectRatio.h" + +#include "mozilla/dom/SVGPreserveAspectRatioBinding.h" +#include "nsContentUtils.h" +#include "nsWhitespaceTokenizer.h" +#include "SVGAnimatedPreserveAspectRatio.h" + +using namespace mozilla::dom; +using namespace mozilla::dom::SVGPreserveAspectRatio_Binding; + +namespace mozilla { + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(DOMSVGPreserveAspectRatio, + mSVGElement) + +static const char* sAlignStrings[] = { + "none", "xMinYMin", "xMidYMin", "xMaxYMin", "xMinYMid", + "xMidYMid", "xMaxYMid", "xMinYMax", "xMidYMax", "xMaxYMax"}; + +static const char* sMeetOrSliceStrings[] = {"meet", "slice"}; + +static uint16_t GetAlignForString(const nsAString& aAlignString) { + for (uint32_t i = 0; i < ArrayLength(sAlignStrings); i++) { + if (aAlignString.EqualsASCII(sAlignStrings[i])) { + return (i + SVG_ALIGN_MIN_VALID); + } + } + + return SVG_PRESERVEASPECTRATIO_UNKNOWN; +} + +static uint16_t GetMeetOrSliceForString(const nsAString& aMeetOrSlice) { + for (uint32_t i = 0; i < ArrayLength(sMeetOrSliceStrings); i++) { + if (aMeetOrSlice.EqualsASCII(sMeetOrSliceStrings[i])) { + return (i + SVG_MEETORSLICE_MIN_VALID); + } + } + + return SVG_MEETORSLICE_UNKNOWN; +} + +/* static */ +nsresult SVGPreserveAspectRatio::FromString(const nsAString& aString, + SVGPreserveAspectRatio* aValue) { + nsWhitespaceTokenizerTemplate tokenizer( + aString); + if (tokenizer.whitespaceBeforeFirstToken() || !tokenizer.hasMoreTokens()) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + const nsAString& token = tokenizer.nextToken(); + + nsresult rv; + SVGPreserveAspectRatio val; + + rv = val.SetAlign(GetAlignForString(token)); + + if (NS_FAILED(rv)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + if (tokenizer.hasMoreTokens()) { + rv = val.SetMeetOrSlice(GetMeetOrSliceForString(tokenizer.nextToken())); + if (NS_FAILED(rv)) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + } else { + val.SetMeetOrSlice(SVG_MEETORSLICE_MEET); + } + + if (tokenizer.whitespaceAfterCurrentToken()) { + return NS_ERROR_DOM_SYNTAX_ERR; + } + + *aValue = val; + return NS_OK; +} + +void SVGPreserveAspectRatio::ToString(nsAString& aValueAsString) const { + MOZ_ASSERT(mAlign >= SVG_ALIGN_MIN_VALID && mAlign <= SVG_ALIGN_MAX_VALID, + "Unknown align"); + aValueAsString.AssignASCII(sAlignStrings[mAlign - SVG_ALIGN_MIN_VALID]); + + if (mAlign != uint8_t(SVG_PRESERVEASPECTRATIO_NONE)) { + MOZ_ASSERT(mMeetOrSlice >= SVG_MEETORSLICE_MIN_VALID && + mMeetOrSlice <= SVG_MEETORSLICE_MAX_VALID, + "Unknown meetOrSlice"); + aValueAsString.Append(' '); + aValueAsString.AppendASCII( + sMeetOrSliceStrings[mMeetOrSlice - SVG_MEETORSLICE_MIN_VALID]); + } +} + +bool SVGPreserveAspectRatio::operator==( + const SVGPreserveAspectRatio& aOther) const { + return mAlign == aOther.mAlign && mMeetOrSlice == aOther.mMeetOrSlice; +} + +JSObject* DOMSVGPreserveAspectRatio::WrapObject( + JSContext* aCx, JS::Handle aGivenProto) { + return mozilla::dom::SVGPreserveAspectRatio_Binding::Wrap(aCx, this, + aGivenProto); +} + +uint16_t DOMSVGPreserveAspectRatio::Align() { + if (mIsBaseValue) { + return mVal->GetBaseValue().GetAlign(); + } + + mSVGElement->FlushAnimations(); + return mVal->GetAnimValue().GetAlign(); +} + +void DOMSVGPreserveAspectRatio::SetAlign(uint16_t aAlign, ErrorResult& rv) { + if (!mIsBaseValue) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + rv = mVal->SetBaseAlign(aAlign, mSVGElement); +} + +uint16_t DOMSVGPreserveAspectRatio::MeetOrSlice() { + if (mIsBaseValue) { + return mVal->GetBaseValue().GetMeetOrSlice(); + } + + mSVGElement->FlushAnimations(); + return mVal->GetAnimValue().GetMeetOrSlice(); +} + +void DOMSVGPreserveAspectRatio::SetMeetOrSlice(uint16_t aMeetOrSlice, + ErrorResult& rv) { + if (!mIsBaseValue) { + rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + } + rv = mVal->SetBaseMeetOrSlice(aMeetOrSlice, mSVGElement); +} + +} // namespace mozilla diff --git a/dom/svg/SVGPreserveAspectRatio.h b/dom/svg/SVGPreserveAspectRatio.h new file mode 100644 index 0000000000..7bcc724e07 --- /dev/null +++ b/dom/svg/SVGPreserveAspectRatio.h @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGPRESERVEASPECTRATIO_H_ +#define DOM_SVG_SVGPRESERVEASPECTRATIO_H_ + +#include "mozilla/dom/SVGPreserveAspectRatioBinding.h" +#include "mozilla/HashFunctions.h" // for HashGeneric + +#include "nsWrapperCache.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/dom/SVGElement.h" + +namespace mozilla { +class ErrorResult; + +// These constants represent the range of valid enum values for the +// parameter. They exclude the sentinel _UNKNOWN value. +const uint16_t SVG_ALIGN_MIN_VALID = + dom::SVGPreserveAspectRatio_Binding::SVG_PRESERVEASPECTRATIO_NONE; +const uint16_t SVG_ALIGN_MAX_VALID = + dom::SVGPreserveAspectRatio_Binding::SVG_PRESERVEASPECTRATIO_XMAXYMAX; + +// These constants represent the range of valid enum values for the +// parameter. They exclude the sentinel _UNKNOWN value. +const uint16_t SVG_MEETORSLICE_MIN_VALID = + dom::SVGPreserveAspectRatio_Binding::SVG_MEETORSLICE_MEET; +const uint16_t SVG_MEETORSLICE_MAX_VALID = + dom::SVGPreserveAspectRatio_Binding::SVG_MEETORSLICE_SLICE; + +class SVGAnimatedPreserveAspectRatio; + +class SVGPreserveAspectRatio final { + friend class SVGAnimatedPreserveAspectRatio; + + public: + explicit SVGPreserveAspectRatio() + : mAlign(dom::SVGPreserveAspectRatio_Binding:: + SVG_PRESERVEASPECTRATIO_UNKNOWN), + mMeetOrSlice( + dom::SVGPreserveAspectRatio_Binding::SVG_MEETORSLICE_UNKNOWN) {} + + SVGPreserveAspectRatio(uint8_t aAlign, uint8_t aMeetOrSlice) + : mAlign(aAlign), mMeetOrSlice(aMeetOrSlice) {} + + static nsresult FromString(const nsAString& aString, + SVGPreserveAspectRatio* aValue); + void ToString(nsAString& aValueAsString) const; + + bool operator==(const SVGPreserveAspectRatio& aOther) const; + + nsresult SetAlign(uint16_t aAlign) { + if (aAlign < SVG_ALIGN_MIN_VALID || aAlign > SVG_ALIGN_MAX_VALID) + return NS_ERROR_FAILURE; + mAlign = static_cast(aAlign); + return NS_OK; + } + + auto GetAlign() const { return mAlign; } + + nsresult SetMeetOrSlice(uint16_t aMeetOrSlice) { + if (aMeetOrSlice < SVG_MEETORSLICE_MIN_VALID || + aMeetOrSlice > SVG_MEETORSLICE_MAX_VALID) + return NS_ERROR_FAILURE; + mMeetOrSlice = static_cast(aMeetOrSlice); + return NS_OK; + } + + auto GetMeetOrSlice() const { return mMeetOrSlice; } + + PLDHashNumber Hash() const { return HashGeneric(mAlign, mMeetOrSlice); } + + private: + // We can't use enum types here because some compilers fail to pack them. + uint8_t mAlign; + uint8_t mMeetOrSlice; +}; + +namespace dom { + +class DOMSVGPreserveAspectRatio final : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGPreserveAspectRatio) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGPreserveAspectRatio) + + DOMSVGPreserveAspectRatio(SVGAnimatedPreserveAspectRatio* aVal, + SVGElement* aSVGElement, bool aIsBaseValue) + : mVal(aVal), mSVGElement(aSVGElement), mIsBaseValue(aIsBaseValue) {} + + // WebIDL + SVGElement* GetParentObject() const { return mSVGElement; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + uint16_t Align(); + void SetAlign(uint16_t aAlign, ErrorResult& rv); + uint16_t MeetOrSlice(); + void SetMeetOrSlice(uint16_t aMeetOrSlice, ErrorResult& rv); + + protected: + ~DOMSVGPreserveAspectRatio(); + + SVGAnimatedPreserveAspectRatio* + mVal; // kept alive because it belongs to mSVGElement + RefPtr mSVGElement; + const bool mIsBaseValue; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGPRESERVEASPECTRATIO_H_ diff --git a/dom/svg/SVGRect.cpp b/dom/svg/SVGRect.cpp new file mode 100644 index 0000000000..ccf643f5e0 --- /dev/null +++ b/dom/svg/SVGRect.cpp @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGRect.h" + +#include "mozilla/dom/SVGRectBinding.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "SVGAnimatedViewBox.h" +#include "nsWrapperCache.h" + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +//---------------------------------------------------------------------- +// nsISupports methods: + +NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(SVGRect, mParent) + +//---------------------------------------------------------------------- +// implementation: + +SVGRect::SVGRect(SVGSVGElement* aSVGElement) + : mVal(nullptr), mParent(aSVGElement), mType(RectType::CreatedValue) { + MOZ_ASSERT(mParent); + mRect = gfx::Rect(0, 0, 0, 0); +} + +JSObject* SVGRect::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + MOZ_ASSERT(mParent); + return SVGRect_Binding::Wrap(aCx, this, aGivenProto); +} + +float SVGRect::X() { + switch (mType) { + case RectType::AnimValue: + static_cast(mParent->AsElement())->FlushAnimations(); + return mVal->GetAnimValue().x; + case RectType::BaseValue: + return mVal->GetBaseValue().x; + default: + return mRect.x; + } +} + +float SVGRect::Y() { + switch (mType) { + case RectType::AnimValue: + static_cast(mParent->AsElement())->FlushAnimations(); + return mVal->GetAnimValue().y; + case RectType::BaseValue: + return mVal->GetBaseValue().y; + default: + return mRect.y; + } +} + +float SVGRect::Width() { + switch (mType) { + case RectType::AnimValue: + static_cast(mParent->AsElement())->FlushAnimations(); + return mVal->GetAnimValue().width; + case RectType::BaseValue: + return mVal->GetBaseValue().width; + default: + return mRect.width; + } +} + +float SVGRect::Height() { + switch (mType) { + case RectType::AnimValue: + static_cast(mParent->AsElement())->FlushAnimations(); + return mVal->GetAnimValue().height; + case RectType::BaseValue: + return mVal->GetBaseValue().height; + default: + return mRect.height; + } +} + +void SVGRect::SetX(float aX, ErrorResult& aRv) { + switch (mType) { + case RectType::AnimValue: + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + case RectType::BaseValue: { + SVGViewBox rect = mVal->GetBaseValue(); + rect.x = aX; + mVal->SetBaseValue(rect, static_cast(mParent->AsElement())); + return; + } + default: + mRect.x = aX; + } +} + +void SVGRect::SetY(float aY, ErrorResult& aRv) { + switch (mType) { + case RectType::AnimValue: + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + case RectType::BaseValue: { + SVGViewBox rect = mVal->GetBaseValue(); + rect.y = aY; + mVal->SetBaseValue(rect, static_cast(mParent->AsElement())); + return; + } + default: + mRect.y = aY; + } +} + +void SVGRect::SetWidth(float aWidth, ErrorResult& aRv) { + switch (mType) { + case RectType::AnimValue: + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + case RectType::BaseValue: { + SVGViewBox rect = mVal->GetBaseValue(); + rect.width = aWidth; + mVal->SetBaseValue(rect, static_cast(mParent->AsElement())); + return; + } + default: + mRect.width = aWidth; + } +} + +void SVGRect::SetHeight(float aHeight, ErrorResult& aRv) { + switch (mType) { + case RectType::AnimValue: + aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return; + case RectType::BaseValue: { + SVGViewBox rect = mVal->GetBaseValue(); + rect.height = aHeight; + mVal->SetBaseValue(rect, static_cast(mParent->AsElement())); + return; + } + default: + mRect.height = aHeight; + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGRect.h b/dom/svg/SVGRect.h new file mode 100644 index 0000000000..924dc0ba9c --- /dev/null +++ b/dom/svg/SVGRect.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGRECT_H_ +#define DOM_SVG_SVGRECT_H_ + +#include "mozilla/dom/SVGElement.h" +#include "mozilla/gfx/Rect.h" + +//////////////////////////////////////////////////////////////////////// +// SVGRect class + +namespace mozilla::dom { + +class SVGSVGElement; + +class SVGRect final : public nsWrapperCache { + public: + enum class RectType : uint8_t { BaseValue, AnimValue, CreatedValue }; + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(SVGRect) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(SVGRect) + + /** + * Generic ctor for objects that are created for an attribute. + */ + SVGRect(SVGAnimatedViewBox* aVal, SVGElement* aSVGElement, RectType aType) + : mVal(aVal), mParent(aSVGElement), mType(aType) { + MOZ_ASSERT(mParent); + MOZ_ASSERT(mType == RectType::BaseValue || mType == RectType::AnimValue); + } + + /** + * Ctor for creating the objects returned by SVGSVGElement.createSVGRect(), + * which do not initially belong to an attribute. + */ + explicit SVGRect(SVGSVGElement* aSVGElement); + + /** + * Ctor for all other non-attribute usage i.e getBBox, getExtentOfChar etc. + */ + SVGRect(nsIContent* aParent, const gfx::Rect& aRect) + : mVal(nullptr), + mRect(aRect), + mParent(aParent), + mType(RectType::CreatedValue) { + MOZ_ASSERT(mParent); + } + + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + float X(); + float Y(); + float Width(); + float Height(); + + void SetX(float aX, mozilla::ErrorResult& aRv); + void SetY(float aY, mozilla::ErrorResult& aRv); + void SetWidth(float aWidth, mozilla::ErrorResult& aRv); + void SetHeight(float aHeight, mozilla::ErrorResult& aRv); + + nsIContent* GetParentObject() const { + MOZ_ASSERT(mParent); + return mParent; + } + + private: + virtual ~SVGRect(); + + // If we're actually representing a viewBox rect then our value + // will come from that element's viewBox attribute's value. + SVGAnimatedViewBox* mVal; // kept alive because it belongs to content + gfx::Rect mRect; + + // If mType is AnimValue or BaseValue this will be an element that + // has a viewBox, otherwise it could be any nsIContent. + RefPtr mParent; + const RectType mType; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGRECT_H_ diff --git a/dom/svg/SVGRectElement.cpp b/dom/svg/SVGRectElement.cpp new file mode 100644 index 0000000000..9f40acff26 --- /dev/null +++ b/dom/svg/SVGRectElement.cpp @@ -0,0 +1,280 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGRectElement.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGRectElementBinding.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/gfx/PathHelpers.h" +#include "nsGkAtoms.h" +#include "SVGGeometryProperty.h" +#include + +NS_IMPL_NS_NEW_SVG_ELEMENT(Rect) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +class DOMSVGAnimatedLength; + +JSObject* SVGRectElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGRectElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGRectElement::sLengthInfo[6] = { + {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::width, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::height, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::rx, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::ry, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGRectElement::SVGRectElement( + already_AddRefed&& aNodeInfo) + : SVGRectElementBase(std::move(aNodeInfo)) {} + +bool SVGRectElement::IsAttributeMapped(const nsAtom* aAttribute) const { + return IsInLengthInfo(aAttribute, sLengthInfo) || + SVGRectElementBase::IsAttributeMapped(aAttribute); +} + +namespace SVGT = SVGGeometryProperty::Tags; + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGRectElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGRectElement::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGRectElement::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGRectElement::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGRectElement::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGRectElement::Rx() { + return mLengthAttributes[ATTR_RX].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGRectElement::Ry() { + return mLengthAttributes[ATTR_RY].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +/* virtual */ +bool SVGRectElement::HasValidDimensions() const { + float width, height; + + if (SVGGeometryProperty::ResolveAll(this, &width, + &height)) { + return width > 0 && height > 0; + } + // This function might be called for an element in display:none subtree + // (e.g. SMIL animateMotion), we fall back to use SVG attributes. + return mLengthAttributes[ATTR_WIDTH].IsExplicitlySet() && + mLengthAttributes[ATTR_WIDTH].GetAnimValInSpecifiedUnits() > 0 && + mLengthAttributes[ATTR_HEIGHT].IsExplicitlySet() && + mLengthAttributes[ATTR_HEIGHT].GetAnimValInSpecifiedUnits() > 0; +} + +SVGElement::LengthAttributesInfo SVGRectElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +//---------------------------------------------------------------------- +// SVGGeometryElement methods + +bool SVGRectElement::GetGeometryBounds(Rect* aBounds, + const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace) { + Rect rect; + Float rx, ry; + + DebugOnly ok = + SVGGeometryProperty::ResolveAll( + this, &rect.x, &rect.y, &rect.width, &rect.height, &rx, &ry); + MOZ_ASSERT(ok, "SVGGeometryProperty::ResolveAll failed"); + + if (rect.IsEmpty()) { + // Rendering of the element disabled + rect.SetEmpty(); // Make sure width/height are zero and not negative + // We still want the x/y position from 'rect' + *aBounds = aToBoundsSpace.TransformBounds(rect); + return true; + } + + if (!aToBoundsSpace.IsRectilinear()) { + // We can't ignore the radii in this case if we want tight bounds + rx = std::max(rx, 0.0f); + ry = std::max(ry, 0.0f); + + if (rx != 0 || ry != 0) { + return false; + } + } + + if (aStrokeOptions.mLineWidth > 0.f) { + if (aToNonScalingStrokeSpace) { + if (aToNonScalingStrokeSpace->IsRectilinear()) { + MOZ_ASSERT(!aToNonScalingStrokeSpace->IsSingular()); + rect = aToNonScalingStrokeSpace->TransformBounds(rect); + // Note that, in principle, an author could cause the corners of the + // rect to be beveled by specifying stroke-linejoin or setting + // stroke-miterlimit to be less than sqrt(2). In that very unlikely + // event the bounds that we calculate here may be too big if + // aToBoundsSpace is non-rectilinear. This is likely to be so rare it's + // not worth handling though. + rect.Inflate(aStrokeOptions.mLineWidth / 2.f); + Matrix nonScalingToBounds = + aToNonScalingStrokeSpace->Inverse() * aToBoundsSpace; + *aBounds = nonScalingToBounds.TransformBounds(rect); + return true; + } + return false; + } + // The "beveled" comment above applies here too + rect.Inflate(aStrokeOptions.mLineWidth / 2.f); + } + + *aBounds = aToBoundsSpace.TransformBounds(rect); + return true; +} + +void SVGRectElement::GetAsSimplePath(SimplePath* aSimplePath) { + float x, y, width, height, rx, ry; + + DebugOnly ok = + SVGGeometryProperty::ResolveAll( + this, &x, &y, &width, &height, &rx, &ry); + MOZ_ASSERT(ok, "SVGGeometryProperty::ResolveAll failed"); + + if (width <= 0 || height <= 0) { + aSimplePath->Reset(); + return; + } + + rx = std::max(rx, 0.0f); + ry = std::max(ry, 0.0f); + + if (rx != 0 || ry != 0) { + aSimplePath->Reset(); + return; + } + + aSimplePath->SetRect(x, y, width, height); +} + +already_AddRefed SVGRectElement::BuildPath(PathBuilder* aBuilder) { + float x, y, width, height, rx, ry; + + if (!SVGGeometryProperty::ResolveAll( + this, &x, &y, &width, &height, &rx, &ry)) { + // This function might be called for element in display:none subtree + // (e.g. getTotalLength), we fall back to use SVG attributes. + GetAnimatedLengthValues(&x, &y, &width, &height, &rx, &ry, nullptr); + // If either the 'rx' or the 'ry' attribute isn't set, then we have to + // set it to the value of the other: + bool hasRx = mLengthAttributes[ATTR_RX].IsExplicitlySet(); + bool hasRy = mLengthAttributes[ATTR_RY].IsExplicitlySet(); + if (hasRx && !hasRy) { + ry = rx; + } else if (hasRy && !hasRx) { + rx = ry; + } + } + + if (width <= 0 || height <= 0) { + return nullptr; + } + + rx = std::max(rx, 0.0f); + ry = std::max(ry, 0.0f); + + if (rx == 0 && ry == 0) { + // Optimization for the no rounded corners case. + Rect r(x, y, width, height); + aBuilder->MoveTo(r.TopLeft()); + aBuilder->LineTo(r.TopRight()); + aBuilder->LineTo(r.BottomRight()); + aBuilder->LineTo(r.BottomLeft()); + aBuilder->Close(); + } else { + // Clamp rx and ry to half the rect's width and height respectively: + rx = std::min(rx, width / 2); + ry = std::min(ry, height / 2); + + RectCornerRadii radii(rx, ry); + AppendRoundedRectToPath(aBuilder, Rect(x, y, width, height), radii); + } + + return aBuilder->Finish(); +} + +bool SVGRectElement::IsLengthChangedViaCSS(const ComputedStyle& aNewStyle, + const ComputedStyle& aOldStyle) { + const auto& newSVGReset = *aNewStyle.StyleSVGReset(); + const auto& oldSVGReset = *aOldStyle.StyleSVGReset(); + const auto& newPosition = *aNewStyle.StylePosition(); + const auto& oldPosition = *aOldStyle.StylePosition(); + return newSVGReset.mX != oldSVGReset.mX || newSVGReset.mY != oldSVGReset.mY || + newPosition.mWidth != oldPosition.mWidth || + newPosition.mHeight != oldPosition.mHeight || + newSVGReset.mRx != oldSVGReset.mRx || + newSVGReset.mRy != oldSVGReset.mRy; +} + +nsCSSPropertyID SVGRectElement::GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum) { + switch (aAttrEnum) { + case ATTR_X: + return eCSSProperty_x; + case ATTR_Y: + return eCSSProperty_y; + case ATTR_WIDTH: + return eCSSProperty_width; + case ATTR_HEIGHT: + return eCSSProperty_height; + case ATTR_RX: + return eCSSProperty_rx; + case ATTR_RY: + return eCSSProperty_ry; + default: + MOZ_ASSERT_UNREACHABLE("Unknown attr enum"); + return eCSSProperty_UNKNOWN; + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGRectElement.h b/dom/svg/SVGRectElement.h new file mode 100644 index 0000000000..7cf4ebc4e1 --- /dev/null +++ b/dom/svg/SVGRectElement.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGRECTELEMENT_H_ +#define DOM_SVG_SVGRECTELEMENT_H_ + +#include "nsCSSPropertyID.h" +#include "SVGAnimatedLength.h" +#include "SVGGeometryElement.h" + +nsresult NS_NewSVGRectElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla { +class ComputedStyle; + +namespace dom { + +using SVGRectElementBase = SVGGeometryElement; + +class SVGRectElement final : public SVGRectElementBase { + protected: + explicit SVGRectElement(already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + friend nsresult(::NS_NewSVGRectElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + public: + NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; + + // SVGSVGElement methods: + bool HasValidDimensions() const override; + + // SVGGeometryElement methods: + bool GetGeometryBounds( + Rect* aBounds, const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; + void GetAsSimplePath(SimplePath* aSimplePath) override; + already_AddRefed BuildPath(PathBuilder* aBuilder = nullptr) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + static bool IsLengthChangedViaCSS(const ComputedStyle& aNewStyle, + const ComputedStyle& aOldStyle); + static nsCSSPropertyID GetCSSPropertyIdForAttrEnum(uint8_t aAttrEnum); + + // WebIDL + already_AddRefed X(); + already_AddRefed Y(); + already_AddRefed Height(); + already_AddRefed Width(); + already_AddRefed Rx(); + already_AddRefed Ry(); + + protected: + LengthAttributesInfo GetLengthInfo() override; + + enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT, ATTR_RX, ATTR_RY }; + SVGAnimatedLength mLengthAttributes[6]; + static LengthInfo sLengthInfo[6]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGRECTELEMENT_H_ diff --git a/dom/svg/SVGSVGElement.cpp b/dom/svg/SVGSVGElement.cpp new file mode 100644 index 0000000000..fb473a1066 --- /dev/null +++ b/dom/svg/SVGSVGElement.cpp @@ -0,0 +1,585 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGSVGElement.h" + +#include "mozilla/ContentEvents.h" +#include "mozilla/dom/BindContext.h" +#include "mozilla/dom/DOMMatrix.h" +#include "mozilla/dom/SVGSVGElementBinding.h" +#include "mozilla/dom/SVGMatrix.h" +#include "mozilla/dom/SVGRect.h" +#include "mozilla/dom/SVGViewElement.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/ISVGDisplayableFrame.h" +#include "mozilla/PresShell.h" +#include "mozilla/SMILAnimationController.h" +#include "mozilla/SMILTimeContainer.h" +#include "mozilla/SVGUtils.h" + +#include "DOMSVGAngle.h" +#include "DOMSVGLength.h" +#include "DOMSVGNumber.h" +#include "DOMSVGPoint.h" +#include "nsFrameSelection.h" +#include "nsIFrame.h" +#include "ISVGSVGFrame.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT_CHECK_PARSER(SVG) + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +using namespace SVGPreserveAspectRatio_Binding; +using namespace SVGSVGElement_Binding; + +SVGEnumMapping SVGSVGElement::sZoomAndPanMap[] = { + {nsGkAtoms::disable, SVG_ZOOMANDPAN_DISABLE}, + {nsGkAtoms::magnify, SVG_ZOOMANDPAN_MAGNIFY}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGSVGElement::sEnumInfo[1] = { + {nsGkAtoms::zoomAndPan, sZoomAndPanMap, SVG_ZOOMANDPAN_MAGNIFY}}; + +JSObject* SVGSVGElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGSVGElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_CYCLE_COLLECTION_CLASS(SVGSVGElement) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGSVGElement, + SVGSVGElementBase) + if (tmp->mTimedDocumentRoot) { + tmp->mTimedDocumentRoot->Unlink(); + } +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGSVGElement, + SVGSVGElementBase) + if (tmp->mTimedDocumentRoot) { + tmp->mTimedDocumentRoot->Traverse(&cb); + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGSVGElement) + NS_INTERFACE_MAP_ENTRY_CONCRETE(SVGSVGElement) +NS_INTERFACE_MAP_END_INHERITING(SVGSVGElementBase); + +NS_IMPL_ADDREF_INHERITED(SVGSVGElement, SVGSVGElementBase) +NS_IMPL_RELEASE_INHERITED(SVGSVGElement, SVGSVGElementBase) + +SVGView::SVGView() { + mZoomAndPan.Init(SVGSVGElement::ZOOMANDPAN, SVG_ZOOMANDPAN_MAGNIFY); + mViewBox.Init(); + mPreserveAspectRatio.Init(); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGSVGElement::SVGSVGElement( + already_AddRefed&& aNodeInfo, + FromParser aFromParser) + : SVGSVGElementBase(std::move(aNodeInfo)), + mCurrentTranslate(0.0f, 0.0f), + mCurrentScale(1.0f), + mStartAnimationOnBindToTree(aFromParser == NOT_FROM_PARSER || + aFromParser == FROM_PARSER_FRAGMENT || + aFromParser == FROM_PARSER_XSLT), + mImageNeedsTransformInvalidation(false) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT_AND_PARSER(SVGSVGElement) + +//---------------------------------------------------------------------- +// nsIDOMSVGSVGElement methods: + +already_AddRefed SVGSVGElement::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGSVGElement::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGSVGElement::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGSVGElement::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +bool SVGSVGElement::UseCurrentView() const { + return mSVGView || mCurrentViewID; +} + +float SVGSVGElement::CurrentScale() const { return mCurrentScale; } + +#define CURRENT_SCALE_MAX 16.0f +#define CURRENT_SCALE_MIN 0.0625f + +void SVGSVGElement::SetCurrentScale(float aCurrentScale) { + // Prevent bizarre behaviour and maxing out of CPU and memory by clamping + aCurrentScale = clamped(aCurrentScale, CURRENT_SCALE_MIN, CURRENT_SCALE_MAX); + + if (aCurrentScale == mCurrentScale) { + return; + } + mCurrentScale = aCurrentScale; + + if (IsRootSVGSVGElement()) { + InvalidateTransformNotifyFrame(); + } +} + +already_AddRefed SVGSVGElement::CurrentTranslate() { + return DOMSVGPoint::GetTranslateTearOff(&mCurrentTranslate, this); +} + +uint32_t SVGSVGElement::SuspendRedraw(uint32_t max_wait_milliseconds) { + // suspendRedraw is a no-op in Mozilla, so it doesn't matter what + // we return + return 1; +} + +void SVGSVGElement::UnsuspendRedraw(uint32_t suspend_handle_id) { + // no-op +} + +void SVGSVGElement::UnsuspendRedrawAll() { + // no-op +} + +void SVGSVGElement::ForceRedraw() { + // no-op +} + +void SVGSVGElement::PauseAnimations() { + if (mTimedDocumentRoot) { + mTimedDocumentRoot->Pause(SMILTimeContainer::PAUSE_SCRIPT); + } + // else we're not the outermost or not bound to a tree, so silently fail +} + +void SVGSVGElement::UnpauseAnimations() { + if (mTimedDocumentRoot) { + mTimedDocumentRoot->Resume(SMILTimeContainer::PAUSE_SCRIPT); + } + // else we're not the outermost or not bound to a tree, so silently fail +} + +bool SVGSVGElement::AnimationsPaused() { + SMILTimeContainer* root = GetTimedDocumentRoot(); + return root && root->IsPausedByType(SMILTimeContainer::PAUSE_SCRIPT); +} + +float SVGSVGElement::GetCurrentTimeAsFloat() { + SMILTimeContainer* root = GetTimedDocumentRoot(); + if (root) { + double fCurrentTimeMs = double(root->GetCurrentTimeAsSMILTime()); + return (float)(fCurrentTimeMs / PR_MSEC_PER_SEC); + } + return 0.f; +} + +void SVGSVGElement::SetCurrentTime(float seconds) { + if (mTimedDocumentRoot) { + // Make sure the timegraph is up-to-date + FlushAnimations(); + double fMilliseconds = double(seconds) * PR_MSEC_PER_SEC; + // Round to nearest whole number before converting, to avoid precision + // errors + SMILTime lMilliseconds = SVGUtils::ClampToInt64(NS_round(fMilliseconds)); + mTimedDocumentRoot->SetCurrentTime(lMilliseconds); + AnimationNeedsResample(); + // Trigger synchronous sample now, to: + // - Make sure we get an up-to-date paint after this method + // - re-enable event firing (it got disabled during seeking, and it + // doesn't get re-enabled until the first sample after the seek -- so + // let's make that happen now.) + FlushAnimations(); + } + // else we're not the outermost or not bound to a tree, so silently fail +} + +void SVGSVGElement::DeselectAll() { + nsIFrame* frame = GetPrimaryFrame(); + if (frame) { + RefPtr frameSelection = frame->GetFrameSelection(); + frameSelection->ClearNormalSelection(); + } +} + +already_AddRefed SVGSVGElement::CreateSVGNumber() { + return do_AddRef(new DOMSVGNumber(this)); +} + +already_AddRefed SVGSVGElement::CreateSVGLength() { + return do_AddRef(new DOMSVGLength()); +} + +already_AddRefed SVGSVGElement::CreateSVGAngle() { + return do_AddRef(new DOMSVGAngle(this)); +} + +already_AddRefed SVGSVGElement::CreateSVGPoint() { + return do_AddRef(new DOMSVGPoint(Point(0, 0))); +} + +already_AddRefed SVGSVGElement::CreateSVGMatrix() { + return do_AddRef(new SVGMatrix()); +} + +already_AddRefed SVGSVGElement::CreateSVGRect() { + return do_AddRef(new SVGRect(this)); +} + +already_AddRefed SVGSVGElement::CreateSVGTransform() { + return do_AddRef(new DOMSVGTransform()); +} + +already_AddRefed SVGSVGElement::CreateSVGTransformFromMatrix( + const DOMMatrix2DInit& matrix, ErrorResult& rv) { + return do_AddRef(new DOMSVGTransform(matrix, rv)); +} + +void SVGSVGElement::DidChangeTranslate() { + if (Document* doc = GetUncomposedDoc()) { + RefPtr presShell = doc->GetPresShell(); + // now dispatch the appropriate event if we are the root element + if (presShell && IsRootSVGSVGElement()) { + nsEventStatus status = nsEventStatus_eIgnore; + WidgetEvent svgScrollEvent(true, eSVGScroll); + presShell->HandleDOMEventWithTarget(this, &svgScrollEvent, &status); + InvalidateTransformNotifyFrame(); + } + } +} + +//---------------------------------------------------------------------- +// SVGZoomAndPanValues +uint16_t SVGSVGElement::ZoomAndPan() const { + return mEnumAttributes[ZOOMANDPAN].GetAnimValue(); +} + +void SVGSVGElement::SetZoomAndPan(uint16_t aZoomAndPan, ErrorResult& rv) { + if (aZoomAndPan == SVG_ZOOMANDPAN_DISABLE || + aZoomAndPan == SVG_ZOOMANDPAN_MAGNIFY) { + ErrorResult nestedRv; + mEnumAttributes[ZOOMANDPAN].SetBaseValue(aZoomAndPan, this, nestedRv); + MOZ_ASSERT(!nestedRv.Failed(), + "We already validated our aZoomAndPan value!"); + return; + } + + rv.ThrowRangeError(); +} + +//---------------------------------------------------------------------- +SMILTimeContainer* SVGSVGElement::GetTimedDocumentRoot() { + if (mTimedDocumentRoot) { + return mTimedDocumentRoot.get(); + } + + // We must not be the outermost element, try to find it + SVGSVGElement* outerSVGElement = SVGContentUtils::GetOuterSVGElement(this); + + if (outerSVGElement) { + return outerSVGElement->GetTimedDocumentRoot(); + } + // invalid structure + return nullptr; +} +//---------------------------------------------------------------------- +// SVGElement +nsresult SVGSVGElement::BindToTree(BindContext& aContext, nsINode& aParent) { + SMILAnimationController* smilController = nullptr; + + if (Document* doc = aContext.GetComposedDoc()) { + if ((smilController = doc->GetAnimationController())) { + // SMIL is enabled in this document + if (WillBeOutermostSVG(aParent)) { + // We'll be the outermost element. We'll need a time container. + if (!mTimedDocumentRoot) { + mTimedDocumentRoot = MakeUnique(); + } + } else { + // We're a child of some other element, so we don't need our own + // time container. However, we need to make sure that we'll get a + // kick-start if we get promoted to be outermost later on. + mTimedDocumentRoot = nullptr; + mStartAnimationOnBindToTree = true; + } + } + } + + nsresult rv = SVGGraphicsElement::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + if (mTimedDocumentRoot && smilController) { + rv = mTimedDocumentRoot->SetParent(smilController); + if (mStartAnimationOnBindToTree) { + mTimedDocumentRoot->Begin(); + mStartAnimationOnBindToTree = false; + } + } + + return rv; +} + +void SVGSVGElement::UnbindFromTree(bool aNullParent) { + if (mTimedDocumentRoot) { + mTimedDocumentRoot->SetParent(nullptr); + } + + SVGGraphicsElement::UnbindFromTree(aNullParent); +} + +SVGAnimatedTransformList* SVGSVGElement::GetAnimatedTransformList( + uint32_t aFlags) { + if (!(aFlags & DO_ALLOCATE) && mSVGView && mSVGView->mTransforms) { + return mSVGView->mTransforms.get(); + } + return SVGGraphicsElement::GetAnimatedTransformList(aFlags); +} + +void SVGSVGElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { + if (aVisitor.mEvent->mMessage == eSVGLoad) { + if (mTimedDocumentRoot) { + mTimedDocumentRoot->Begin(); + // Set 'resample needed' flag, so that if any script calls a DOM method + // that requires up-to-date animations before our first sample callback, + // we'll force a synchronous sample. + AnimationNeedsResample(); + } + } + SVGSVGElementBase::GetEventTargetParent(aVisitor); +} + +bool SVGSVGElement::IsEventAttributeNameInternal(nsAtom* aName) { + /* The events in EventNameType_SVGSVG are for events that are only + applicable to outermost 'svg' elements. We don't check if we're an outer + 'svg' element in case we're not inserted into the document yet, but since + the target of the events in question will always be the outermost 'svg' + element, this shouldn't cause any real problems. + */ + return nsContentUtils::IsEventAttributeName( + aName, (EventNameType_SVGGraphic | EventNameType_SVGSVG)); +} + +//---------------------------------------------------------------------- +// public helpers: + +int32_t SVGSVGElement::GetIntrinsicWidth() { + if (mLengthAttributes[ATTR_WIDTH].IsPercentage()) { + return -1; + } + // Passing |this| as a SVGViewportElement* invokes the variant of GetAnimValue + // that uses the passed argument as the context, but that's fine since we + // know the length isn't a percentage so the context won't be used (and we + // need to pass the element to be able to resolve em/ex units). + float width = mLengthAttributes[ATTR_WIDTH].GetAnimValue(this); + return SVGUtils::ClampToInt(width); +} + +int32_t SVGSVGElement::GetIntrinsicHeight() { + if (mLengthAttributes[ATTR_HEIGHT].IsPercentage()) { + return -1; + } + // Passing |this| as a SVGViewportElement* invokes the variant of GetAnimValue + // that uses the passed argument as the context, but that's fine since we + // know the length isn't a percentage so the context won't be used (and we + // need to pass the element to be able to resolve em/ex units). + float height = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(this); + return SVGUtils::ClampToInt(height); +} + +void SVGSVGElement::FlushImageTransformInvalidation() { + MOZ_ASSERT(!GetParent(), "Should only be called on root node"); + MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(), + "Should only be called on image documents"); + + if (mImageNeedsTransformInvalidation) { + InvalidateTransformNotifyFrame(); + mImageNeedsTransformInvalidation = false; + } +} + +//---------------------------------------------------------------------- +// implementation helpers + +bool SVGSVGElement::WillBeOutermostSVG(nsINode& aParent) const { + nsINode* parent = &aParent; + while (parent && parent->IsSVGElement()) { + if (parent->IsSVGElement(nsGkAtoms::foreignObject)) { + // SVG in a foreignObject must have its own (SVGOuterSVGFrame). + return false; + } + if (parent->IsSVGElement(nsGkAtoms::svg)) { + return false; + } + parent = parent->GetParentOrShadowHostNode(); + } + + return true; +} + +void SVGSVGElement::InvalidateTransformNotifyFrame() { + ISVGSVGFrame* svgframe = do_QueryFrame(GetPrimaryFrame()); + // might fail this check if we've failed conditional processing + if (svgframe) { + svgframe->NotifyViewportOrTransformChanged( + ISVGDisplayableFrame::TRANSFORM_CHANGED); + } +} + +SVGElement::EnumAttributesInfo SVGSVGElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +void SVGSVGElement::SetImageOverridePreserveAspectRatio( + const SVGPreserveAspectRatio& aPAR) { + MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(), + "should only override preserveAspectRatio in images"); + + bool hasViewBox = HasViewBox(); + if (!hasViewBox && ShouldSynthesizeViewBox()) { + // My non- clients will have been painting me with a synthesized + // viewBox, but my client that's about to paint me now does NOT + // want that. Need to tell ourselves to flush our transform. + mImageNeedsTransformInvalidation = true; + } + + if (!hasViewBox) { + return; // preserveAspectRatio irrelevant (only matters if we have viewBox) + } + + if (SetPreserveAspectRatioProperty(aPAR)) { + mImageNeedsTransformInvalidation = true; + } +} + +void SVGSVGElement::ClearImageOverridePreserveAspectRatio() { + MOZ_ASSERT(OwnerDoc()->IsBeingUsedAsImage(), + "should only override image preserveAspectRatio in images"); + + if (!HasViewBox() && ShouldSynthesizeViewBox()) { + // My non- clients will want to paint me with a synthesized + // viewBox, but my client that just painted me did NOT + // use that. Need to tell ourselves to flush our transform. + mImageNeedsTransformInvalidation = true; + } + + if (ClearPreserveAspectRatioProperty()) { + mImageNeedsTransformInvalidation = true; + } +} + +bool SVGSVGElement::SetPreserveAspectRatioProperty( + const SVGPreserveAspectRatio& aPAR) { + SVGPreserveAspectRatio* pAROverridePtr = new SVGPreserveAspectRatio(aPAR); + nsresult rv = + SetProperty(nsGkAtoms::overridePreserveAspectRatio, pAROverridePtr, + nsINode::DeleteProperty, true); + MOZ_ASSERT(rv != NS_PROPTABLE_PROP_OVERWRITTEN, + "Setting override value when it's already set...?"); + + if (MOZ_UNLIKELY(NS_FAILED(rv))) { + // property-insertion failed (e.g. OOM in property-table code) + delete pAROverridePtr; + return false; + } + return true; +} + +const SVGPreserveAspectRatio* SVGSVGElement::GetPreserveAspectRatioProperty() + const { + void* valPtr = GetProperty(nsGkAtoms::overridePreserveAspectRatio); + if (valPtr) { + return static_cast(valPtr); + } + return nullptr; +} + +bool SVGSVGElement::ClearPreserveAspectRatioProperty() { + void* valPtr = TakeProperty(nsGkAtoms::overridePreserveAspectRatio); + bool didHaveProperty = !!valPtr; + delete static_cast(valPtr); + return didHaveProperty; +} + +SVGPreserveAspectRatio SVGSVGElement::GetPreserveAspectRatioWithOverride() + const { + Document* doc = GetUncomposedDoc(); + if (doc && doc->IsBeingUsedAsImage()) { + const SVGPreserveAspectRatio* pAROverridePtr = + GetPreserveAspectRatioProperty(); + if (pAROverridePtr) { + return *pAROverridePtr; + } + } + + SVGViewElement* viewElement = GetCurrentViewElement(); + + // This check is equivalent to "!HasViewBox() && + // ShouldSynthesizeViewBox()". We're just holding onto the viewElement that + // HasViewBox() would look up, so that we don't have to look it up again + // later. + if (!((viewElement && viewElement->mViewBox.HasRect()) || + (mSVGView && mSVGView->mViewBox.HasRect()) || mViewBox.HasRect()) && + ShouldSynthesizeViewBox()) { + // If we're synthesizing a viewBox, use preserveAspectRatio="none"; + return SVGPreserveAspectRatio(SVG_PRESERVEASPECTRATIO_NONE, + SVG_MEETORSLICE_SLICE); + } + + if (viewElement && viewElement->mPreserveAspectRatio.IsExplicitlySet()) { + return viewElement->mPreserveAspectRatio.GetAnimValue(); + } + if (mSVGView && mSVGView->mPreserveAspectRatio.IsExplicitlySet()) { + return mSVGView->mPreserveAspectRatio.GetAnimValue(); + } + return mPreserveAspectRatio.GetAnimValue(); +} + +SVGViewElement* SVGSVGElement::GetCurrentViewElement() const { + if (mCurrentViewID) { + // XXXsmaug It is unclear how this should work in case we're in Shadow DOM. + Document* doc = GetUncomposedDoc(); + if (doc) { + Element* element = doc->GetElementById(*mCurrentViewID); + return SVGViewElement::FromNodeOrNull(element); + } + } + return nullptr; +} + +const SVGAnimatedViewBox& SVGSVGElement::GetViewBoxInternal() const { + SVGViewElement* viewElement = GetCurrentViewElement(); + + if (viewElement && viewElement->mViewBox.HasRect()) { + return viewElement->mViewBox; + } + if (mSVGView && mSVGView->mViewBox.HasRect()) { + return mSVGView->mViewBox; + } + + return mViewBox; +} + +SVGAnimatedTransformList* SVGSVGElement::GetTransformInternal() const { + return (mSVGView && mSVGView->mTransforms) ? mSVGView->mTransforms.get() + : mTransforms.get(); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGSVGElement.h b/dom/svg/SVGSVGElement.h new file mode 100644 index 0000000000..74cc73b5d0 --- /dev/null +++ b/dom/svg/SVGSVGElement.h @@ -0,0 +1,284 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGSVGELEMENT_H_ +#define DOM_SVG_SVGSVGELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGViewportElement.h" + +nsresult NS_NewSVGSVGElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo, + mozilla::dom::FromParser aFromParser); + +// {4b83982c-e5e9-4ca1-abd4-14d27e8b3531} +#define MOZILLA_SVGSVGELEMENT_IID \ + { \ + 0x4b83982c, 0xe5e9, 0x4ca1, { \ + 0xab, 0xd4, 0x14, 0xd2, 0x7e, 0x8b, 0x35, 0x31 \ + } \ + } + +namespace mozilla { +class AutoSVGViewHandler; +class SMILTimeContainer; +class SVGFragmentIdentifier; +class EventChainPreVisitor; + +namespace dom { +struct DOMMatrix2DInit; +class DOMSVGAngle; +class DOMSVGLength; +class DOMSVGNumber; +class DOMSVGPoint; +class SVGMatrix; +class SVGRect; +class SVGSVGElement; + +// Stores svgView arguments of SVG fragment identifiers. +class SVGView { + public: + SVGView(); + + SVGAnimatedEnumeration mZoomAndPan; + SVGAnimatedViewBox mViewBox; + SVGAnimatedPreserveAspectRatio mPreserveAspectRatio; + UniquePtr mTransforms; +}; + +using SVGSVGElementBase = SVGViewportElement; + +class SVGSVGElement final : public SVGSVGElementBase { + friend class mozilla::SVGFragmentIdentifier; + friend class mozilla::SVGOuterSVGFrame; + friend class mozilla::AutoSVGViewHandler; + friend class mozilla::AutoPreserveAspectRatioOverride; + friend class mozilla::dom::SVGView; + + protected: + SVGSVGElement(already_AddRefed&& aNodeInfo, + FromParser aFromParser); + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + friend nsresult(::NS_NewSVGSVGElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo, + mozilla::dom::FromParser aFromParser)); + + ~SVGSVGElement() = default; + + public: + NS_IMPL_FROMNODE_WITH_TAG(SVGSVGElement, kNameSpaceID_SVG, svg) + + // interfaces: + NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_SVGSVGELEMENT_IID) + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SVGSVGElement, SVGSVGElementBase) + + /* + * Send appropriate events and updates if our root translate + * has changed. + */ + MOZ_CAN_RUN_SCRIPT + void DidChangeTranslate(); + + // nsIContent interface + void GetEventTargetParent(EventChainPreVisitor& aVisitor) override; + bool IsEventAttributeNameInternal(nsAtom* aName) override; + + // nsINode methods: + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed X(); + already_AddRefed Y(); + already_AddRefed Width(); + already_AddRefed Height(); + bool UseCurrentView() const; + float CurrentScale() const; + void SetCurrentScale(float aCurrentScale); + already_AddRefed CurrentTranslate(); + uint32_t SuspendRedraw(uint32_t max_wait_milliseconds); + void UnsuspendRedraw(uint32_t suspend_handle_id); + void UnsuspendRedrawAll(); + void ForceRedraw(); + void PauseAnimations(); + void UnpauseAnimations(); + bool AnimationsPaused(); + float GetCurrentTimeAsFloat(); + void SetCurrentTime(float seconds); + void DeselectAll(); + already_AddRefed CreateSVGNumber(); + already_AddRefed CreateSVGLength(); + already_AddRefed CreateSVGAngle(); + already_AddRefed CreateSVGPoint(); + already_AddRefed CreateSVGMatrix(); + already_AddRefed CreateSVGRect(); + already_AddRefed CreateSVGTransform(); + already_AddRefed CreateSVGTransformFromMatrix( + const DOMMatrix2DInit& matrix, ErrorResult& rv); + using nsINode::GetElementById; // This does what we want + uint16_t ZoomAndPan() const; + void SetZoomAndPan(uint16_t aZoomAndPan, ErrorResult& rv); + + // SVGElement overrides + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + void UnbindFromTree(bool aNullParent) override; + SVGAnimatedTransformList* GetAnimatedTransformList( + uint32_t aFlags = 0) override; + + // SVGSVGElement methods: + + // Returns true IFF our attributes are currently overridden by a + // element and that element's ID matches the passed-in string. + bool IsOverriddenBy(const nsAString& aViewID) const { + return mCurrentViewID && mCurrentViewID->Equals(aViewID); + } + + SMILTimeContainer* GetTimedDocumentRoot(); + + // public helpers: + + const SVGPoint& GetCurrentTranslate() const { return mCurrentTranslate; } + bool IsScaledOrTranslated() const { + return mCurrentTranslate != SVGPoint() || mCurrentScale != 1.0f; + } + + /** + * Returns -1 if the width/height is a percentage, else returns the user unit + * length clamped to fit in a int32_t. + * XXX see bug 1112533 comment 3 - we should fix drawImage so that we can + * change these methods to make zero the error flag for percentages. + */ + int32_t GetIntrinsicWidth(); + int32_t GetIntrinsicHeight(); + + // This services any pending notifications for the transform on on this root + // node needing to be recalculated. (Only applicable in + // SVG-as-an-image documents.) + virtual void FlushImageTransformInvalidation(); + + private: + // SVGViewportElement methods: + + virtual SVGViewElement* GetCurrentViewElement() const; + SVGPreserveAspectRatio GetPreserveAspectRatioWithOverride() const override; + + // implementation helpers: + + /* + * While binding to the tree we need to determine if we will be the outermost + * element _before_ the children are bound (as they want to know what + * timed document root to register with) and therefore _before_ our parent is + * set (both actions are performed by Element::BindToTree) so we + * can't use GetOwnerSVGElement() as it relies on GetParent(). This code is + * basically a simplified version of GetOwnerSVGElement that uses the parent + * parameters passed in instead. + * + * FIXME(bug 1596690): GetOwnerSVGElement() uses the flattened tree parent + * rather than the DOM tree parent nowadays. + */ + bool WillBeOutermostSVG(nsINode& aParent) const; + + // invalidate viewbox -> viewport xform & inform frames + void InvalidateTransformNotifyFrame(); + + // Methods for elements to override my "PreserveAspectRatio" value. + // These are private so that only our friends + // (AutoPreserveAspectRatioOverride in particular) have access. + void SetImageOverridePreserveAspectRatio(const SVGPreserveAspectRatio& aPAR); + void ClearImageOverridePreserveAspectRatio(); + + // Set/Clear properties to hold old version of preserveAspectRatio + // when it's being overridden by an element that we are inside of. + bool SetPreserveAspectRatioProperty(const SVGPreserveAspectRatio& aPAR); + const SVGPreserveAspectRatio* GetPreserveAspectRatioProperty() const; + bool ClearPreserveAspectRatioProperty(); + + const SVGAnimatedViewBox& GetViewBoxInternal() const override; + SVGAnimatedTransformList* GetTransformInternal() const override; + + EnumAttributesInfo GetEnumInfo() override; + + enum { ZOOMANDPAN }; + SVGAnimatedEnumeration mEnumAttributes[1]; + static SVGEnumMapping sZoomAndPanMap[]; + static EnumInfo sEnumInfo[1]; + + // The time container for animations within this SVG document fragment. Set + // for all outermost elements (not nested elements). + UniquePtr mTimedDocumentRoot; + + SVGPoint mCurrentTranslate; + float mCurrentScale; + + // For outermost elements created from parsing, animation is started by + // the onload event in accordance with the SVG spec, but for elements + // created by script or promoted from inner to outermost we need + // to manually kick off animation when they are bound to the tree. + bool mStartAnimationOnBindToTree; + + bool mImageNeedsTransformInvalidation; + + // mCurrentViewID and mSVGView are mutually exclusive; we can have + // at most one non-null. + UniquePtr mCurrentViewID; + UniquePtr mSVGView; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(SVGSVGElement, MOZILLA_SVGSVGELEMENT_IID) + +} // namespace dom + +class MOZ_RAII AutoSVGTimeSetRestore { + public: + AutoSVGTimeSetRestore(dom::SVGSVGElement* aRootElem, float aFrameTime) + : mRootElem(aRootElem), + mOriginalTime(mRootElem->GetCurrentTimeAsFloat()) { + mRootElem->SetCurrentTime( + aFrameTime); // Does nothing if there's no change. + } + + ~AutoSVGTimeSetRestore() { mRootElem->SetCurrentTime(mOriginalTime); } + + private: + const RefPtr mRootElem; + const float mOriginalTime; +}; + +class MOZ_RAII AutoPreserveAspectRatioOverride { + public: + AutoPreserveAspectRatioOverride(const SVGImageContext& aSVGContext, + dom::SVGSVGElement* aRootElem) + : mRootElem(aRootElem), mDidOverride(false) { + MOZ_ASSERT(mRootElem, "No SVG/Symbol node to manage?"); + + if (aSVGContext.GetPreserveAspectRatio().isSome()) { + // Override preserveAspectRatio in our helper document. + // XXXdholbert We should technically be overriding the helper doc's clip + // and overflow properties here, too. See bug 272288 comment 36. + mRootElem->SetImageOverridePreserveAspectRatio( + *aSVGContext.GetPreserveAspectRatio()); + mDidOverride = true; + } + } + + ~AutoPreserveAspectRatioOverride() { + if (mDidOverride) { + mRootElem->ClearImageOverridePreserveAspectRatio(); + } + } + + private: + const RefPtr mRootElem; + bool mDidOverride; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGSVGELEMENT_H_ diff --git a/dom/svg/SVGScriptElement.cpp b/dom/svg/SVGScriptElement.cpp new file mode 100644 index 0000000000..9f5bb92088 --- /dev/null +++ b/dom/svg/SVGScriptElement.cpp @@ -0,0 +1,208 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGScriptElement.h" + +#include "nsGkAtoms.h" +#include "nsNetUtil.h" +#include "nsContentUtils.h" +#include "mozilla/dom/SVGScriptElementBinding.h" +#include "nsIScriptError.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT_CHECK_PARSER(Script) + +namespace mozilla::dom { + +JSObject* SVGScriptElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGScriptElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::StringInfo SVGScriptElement::sStringInfo[2] = { + {nsGkAtoms::href, kNameSpaceID_None, false}, + {nsGkAtoms::href, kNameSpaceID_XLink, false}}; + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ISUPPORTS_INHERITED(SVGScriptElement, SVGScriptElementBase, + nsIScriptLoaderObserver, nsIScriptElement, + nsIMutationObserver) + +//---------------------------------------------------------------------- +// Implementation + +SVGScriptElement::SVGScriptElement( + already_AddRefed&& aNodeInfo, + FromParser aFromParser) + : SVGScriptElementBase(std::move(aNodeInfo)), ScriptElement(aFromParser) { + AddMutationObserver(this); +} + +//---------------------------------------------------------------------- +// nsINode methods + +nsresult SVGScriptElement::Clone(dom::NodeInfo* aNodeInfo, + nsINode** aResult) const { + *aResult = nullptr; + + SVGScriptElement* it = new (aNodeInfo->NodeInfoManager()) + SVGScriptElement(do_AddRef(aNodeInfo), NOT_FROM_PARSER); + + nsCOMPtr kungFuDeathGrip = it; + nsresult rv1 = it->Init(); + nsresult rv2 = const_cast(this)->CopyInnerTo(it); + NS_ENSURE_SUCCESS(rv1, rv1); + NS_ENSURE_SUCCESS(rv2, rv2); + + // The clone should be marked evaluated if we are. + it->mAlreadyStarted = mAlreadyStarted; + it->mLineNumber = mLineNumber; + it->mMalformed = mMalformed; + + kungFuDeathGrip.swap(*aResult); + + return NS_OK; +} + +//---------------------------------------------------------------------- +void SVGScriptElement::GetType(nsAString& aType) { GetScriptType(aType); } + +void SVGScriptElement::SetType(const nsAString& aType, ErrorResult& rv) { + rv = SetAttr(kNameSpaceID_None, nsGkAtoms::type, aType, true); +} + +void SVGScriptElement::GetCrossOrigin(nsAString& aCrossOrigin) { + // Null for both missing and invalid defaults is ok, since we + // always parse to an enum value, so we don't need an invalid + // default, and we _want_ the missing default to be null. + GetEnumAttr(nsGkAtoms::crossorigin, nullptr, aCrossOrigin); +} + +void SVGScriptElement::SetCrossOrigin(const nsAString& aCrossOrigin, + ErrorResult& aError) { + SetOrRemoveNullableStringAttr(nsGkAtoms::crossorigin, aCrossOrigin, aError); +} + +already_AddRefed SVGScriptElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- +// nsIScriptElement methods + +bool SVGScriptElement::GetScriptType(nsAString& type) { + return GetAttr(kNameSpaceID_None, nsGkAtoms::type, type); +} + +void SVGScriptElement::GetScriptText(nsAString& text) const { + nsContentUtils::GetNodeTextContent(this, false, text); +} + +void SVGScriptElement::GetScriptCharset(nsAString& charset) { + charset.Truncate(); +} + +void SVGScriptElement::FreezeExecutionAttrs(Document* aOwnerDoc) { + if (mFrozen) { + return; + } + + if (mStringAttributes[HREF].IsExplicitlySet() || + mStringAttributes[XLINK_HREF].IsExplicitlySet()) { + // variation of this code in nsHTMLScriptElement - check if changes + // need to be transferred when modifying + bool isHref = false; + nsAutoString src; + if (mStringAttributes[HREF].IsExplicitlySet()) { + mStringAttributes[HREF].GetAnimValue(src, this); + isHref = true; + } else { + mStringAttributes[XLINK_HREF].GetAnimValue(src, this); + } + + // Empty src should be treated as invalid URL. + if (!src.IsEmpty()) { + NS_NewURI(getter_AddRefs(mUri), src, nullptr, GetBaseURI()); + + if (!mUri) { + AutoTArray params = { + isHref ? u"href"_ns : u"xlink:href"_ns, src}; + + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "SVG"_ns, OwnerDoc(), + nsContentUtils::eDOM_PROPERTIES, "ScriptSourceInvalidUri", params, + nullptr, u""_ns, GetScriptLineNumber(), GetScriptColumnNumber()); + } + } else { + AutoTArray params = {isHref ? u"href"_ns : u"xlink:href"_ns}; + + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "SVG"_ns, OwnerDoc(), + nsContentUtils::eDOM_PROPERTIES, "ScriptSourceEmpty", params, nullptr, + u""_ns, GetScriptLineNumber(), GetScriptColumnNumber()); + } + + // At this point mUri will be null for invalid URLs. + mExternal = true; + } + + mFrozen = true; +} + +//---------------------------------------------------------------------- +// ScriptElement methods + +bool SVGScriptElement::HasScriptContent() { + return (mFrozen ? mExternal + : mStringAttributes[HREF].IsExplicitlySet() || + mStringAttributes[XLINK_HREF].IsExplicitlySet()) || + nsContentUtils::HasNonEmptyTextContent(this); +} + +//---------------------------------------------------------------------- +// SVGElement methods + +SVGElement::StringAttributesInfo SVGScriptElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +nsresult SVGScriptElement::BindToTree(BindContext& aContext, nsINode& aParent) { + nsresult rv = SVGScriptElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + if (IsInComposedDoc()) { + MaybeProcessScript(); + } + + return NS_OK; +} + +bool SVGScriptElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) { + if (aNamespaceID == kNameSpaceID_None && + aAttribute == nsGkAtoms::crossorigin) { + ParseCORSValue(aValue, aResult); + return true; + } + + return SVGScriptElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue, + aMaybeScriptedPrincipal, aResult); +} + +CORSMode SVGScriptElement::GetCORSMode() const { + return AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGScriptElement.h b/dom/svg/SVGScriptElement.h new file mode 100644 index 0000000000..34b6d650e9 --- /dev/null +++ b/dom/svg/SVGScriptElement.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGSCRIPTELEMENT_H_ +#define DOM_SVG_SVGSCRIPTELEMENT_H_ + +#include "SVGAnimatedString.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/dom/ScriptElement.h" +#include "mozilla/dom/SVGElement.h" + +nsresult NS_NewSVGScriptElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo, + mozilla::dom::FromParser aFromParser); + +namespace mozilla::dom { + +using SVGScriptElementBase = SVGElement; + +class SVGScriptElement final : public SVGScriptElementBase, + public ScriptElement { + protected: + friend nsresult(::NS_NewSVGScriptElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo, + mozilla::dom::FromParser aFromParser)); + SVGScriptElement(already_AddRefed&& aNodeInfo, + FromParser aFromParser); + + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + // interfaces: + + NS_DECL_ISUPPORTS_INHERITED + + // nsIScriptElement + bool GetScriptType(nsAString& type) override; + void GetScriptText(nsAString& text) const override; + void GetScriptCharset(nsAString& charset) override; + void FreezeExecutionAttrs(Document* aOwnerDoc) override; + CORSMode GetCORSMode() const override; + + // ScriptElement + bool HasScriptContent() override; + + // nsIContent specializations: + nsresult BindToTree(BindContext&, nsINode& aParent) override; + bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + void GetType(nsAString& aType); + void SetType(const nsAString& aType, ErrorResult& rv); + void GetCrossOrigin(nsAString& aCrossOrigin); + void SetCrossOrigin(const nsAString& aCrossOrigin, ErrorResult& aError); + already_AddRefed Href(); + + protected: + ~SVGScriptElement() = default; + + StringAttributesInfo GetStringInfo() override; + + // SVG Script elements don't have the ability to set async properties on + // themselves, so this will always return false. + bool GetAsyncState() override { return false; } + + nsIContent* GetAsContent() override { return this; } + + enum { HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGSCRIPTELEMENT_H_ diff --git a/dom/svg/SVGSetElement.cpp b/dom/svg/SVGSetElement.cpp new file mode 100644 index 0000000000..420f9e707c --- /dev/null +++ b/dom/svg/SVGSetElement.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGSetElement.h" +#include "mozilla/dom/SVGSetElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Set) + +namespace mozilla::dom { + +JSObject* SVGSetElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGSetElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGSetElement::SVGSetElement( + already_AddRefed&& aNodeInfo) + : SVGAnimationElement(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGSetElement) + +//---------------------------------------------------------------------- + +SMILAnimationFunction& SVGSetElement::AnimationFunction() { + return mAnimationFunction; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGSetElement.h b/dom/svg/SVGSetElement.h new file mode 100644 index 0000000000..7dc9bfb8d5 --- /dev/null +++ b/dom/svg/SVGSetElement.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGSETELEMENT_H_ +#define DOM_SVG_SVGSETELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/dom/SVGAnimationElement.h" +#include "mozilla/SMILSetAnimationFunction.h" + +nsresult NS_NewSVGSetElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +class SVGSetElement final : public SVGAnimationElement { + protected: + explicit SVGSetElement(already_AddRefed&& aNodeInfo); + + SMILSetAnimationFunction mAnimationFunction; + + friend nsresult(::NS_NewSVGSetElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + // nsINode + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // SVGAnimationElement + SMILAnimationFunction& AnimationFunction() override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGSETELEMENT_H_ diff --git a/dom/svg/SVGStopElement.cpp b/dom/svg/SVGStopElement.cpp new file mode 100644 index 0000000000..4c92cdbfde --- /dev/null +++ b/dom/svg/SVGStopElement.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGStopElement.h" +#include "mozilla/dom/SVGStopElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Stop) + +namespace mozilla::dom { + +JSObject* SVGStopElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGStopElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::NumberInfo SVGStopElement::sNumberInfo = {nsGkAtoms::offset, 0, + true}; + +//---------------------------------------------------------------------- +// Implementation + +SVGStopElement::SVGStopElement( + already_AddRefed&& aNodeInfo) + : SVGStopElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGStopElement) + +//---------------------------------------------------------------------- + +already_AddRefed SVGStopElement::Offset() { + return mOffset.ToDOMAnimatedNumber(this); +} + +//---------------------------------------------------------------------- +// sSVGElement methods + +SVGElement::NumberAttributesInfo SVGStopElement::GetNumberInfo() { + return NumberAttributesInfo(&mOffset, &sNumberInfo, 1); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGStopElement.h b/dom/svg/SVGStopElement.h new file mode 100644 index 0000000000..e712c3a6fe --- /dev/null +++ b/dom/svg/SVGStopElement.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGSTOPELEMENT_H_ +#define DOM_SVG_SVGSTOPELEMENT_H_ + +#include "mozilla/dom/SVGElement.h" +#include "SVGAnimatedNumber.h" + +nsresult NS_NewSVGStopElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGStopElementBase = SVGElement; + +class SVGStopElement final : public SVGStopElementBase { + protected: + friend nsresult(::NS_NewSVGStopElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGStopElement(already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + // nsIContent interface + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // WebIDL + already_AddRefed Offset(); + + protected: + NumberAttributesInfo GetNumberInfo() override; + SVGAnimatedNumber mOffset; + static NumberInfo sNumberInfo; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGSTOPELEMENT_H_ diff --git a/dom/svg/SVGStringList.cpp b/dom/svg/SVGStringList.cpp new file mode 100644 index 0000000000..f0db815569 --- /dev/null +++ b/dom/svg/SVGStringList.cpp @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGStringList.h" +#include "nsError.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsWhitespaceTokenizer.h" +#include "SVGContentUtils.h" + +namespace mozilla { + +nsresult SVGStringList::CopyFrom(const SVGStringList& rhs) { + if (!mStrings.Assign(rhs.mStrings, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + mIsSet = true; + return NS_OK; +} + +void SVGStringList::GetValue(nsAString& aValue) const { + aValue = StringJoin(mIsCommaSeparated ? u", "_ns : u" "_ns, mStrings); +} + +nsresult SVGStringList::SetValue(const nsAString& aValue) { + SVGStringList temp; + + if (mIsCommaSeparated) { + nsCharSeparatedTokenizerTemplate + tokenizer(aValue, ','); + + while (tokenizer.hasMoreTokens()) { + if (!temp.AppendItem(tokenizer.nextToken())) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + if (tokenizer.separatorAfterCurrentToken()) { + return NS_ERROR_DOM_SYNTAX_ERR; // trailing comma + } + } else { + nsWhitespaceTokenizerTemplate tokenizer( + aValue); + + while (tokenizer.hasMoreTokens()) { + if (!temp.AppendItem(tokenizer.nextToken())) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + + return CopyFrom(temp); +} + +} // namespace mozilla diff --git a/dom/svg/SVGStringList.h b/dom/svg/SVGStringList.h new file mode 100644 index 0000000000..cedf42aa60 --- /dev/null +++ b/dom/svg/SVGStringList.h @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGSTRINGLIST_H_ +#define DOM_SVG_SVGSTRINGLIST_H_ + +#include "nsDebug.h" +#include "nsTArray.h" +#include "nsString.h" // IWYU pragma: keep + +namespace mozilla { + +namespace dom { +class DOMSVGStringList; +} + +/** + * + * The DOM wrapper class for this class is DOMSVGStringList. + */ +class SVGStringList { + friend class dom::DOMSVGStringList; + + public: + SVGStringList() : mIsSet(false), mIsCommaSeparated(false) {} + ~SVGStringList() = default; + + void SetIsCommaSeparated(bool aIsCommaSeparated) { + mIsCommaSeparated = aIsCommaSeparated; + } + nsresult SetValue(const nsAString& aValue); + + void Clear() { + mStrings.Clear(); + mIsSet = false; + } + + /// This may return an incomplete string on OOM, but that's acceptable. + void GetValue(nsAString& aValue) const; + + bool IsEmpty() const { return mStrings.IsEmpty(); } + + uint32_t Length() const { return mStrings.Length(); } + + const nsAString& operator[](uint32_t aIndex) const { + return mStrings[aIndex]; + } + + bool operator==(const SVGStringList& rhs) const { + return mStrings == rhs.mStrings; + } + + bool SetCapacity(uint32_t size) { + return mStrings.SetCapacity(size, fallible); + } + + void Compact() { mStrings.Compact(); } + + // Returns true if the value of this stringlist has been explicitly + // set by markup or a DOM call, false otherwise. + bool IsExplicitlySet() const { return mIsSet; } + + // Access to methods that can modify objects of this type is deliberately + // limited. This is to reduce the chances of someone modifying objects of + // this type without taking the necessary steps to keep DOM wrappers in sync. + // If you need wider access to these methods, consider adding a method to + // DOMSVGAnimatedStringList and having that class act as an intermediary so it + // can take care of keeping DOM wrappers in sync. + + protected: + /** + * This may fail on OOM if the internal capacity needs to be increased, in + * which case the list will be left unmodified. + */ + nsresult CopyFrom(const SVGStringList& rhs); + + nsAString& operator[](uint32_t aIndex) { return mStrings[aIndex]; } + + /** + * This may fail (return false) on OOM if the internal capacity is being + * increased, in which case the list will be left unmodified. + */ + bool SetLength(uint32_t aStringOfItems) { + return mStrings.SetLength(aStringOfItems, fallible); + } + + private: + // Marking the following private only serves to show which methods are only + // used by our friend classes (as opposed to our subclasses) - it doesn't + // really provide additional safety. + + bool InsertItem(uint32_t aIndex, const nsAString& aString) { + if (aIndex >= mStrings.Length()) { + aIndex = mStrings.Length(); + } + if (mStrings.InsertElementAt(aIndex, aString, fallible)) { + mIsSet = true; + return true; + } + return false; + } + + void ReplaceItem(uint32_t aIndex, const nsAString& aString) { + MOZ_ASSERT(aIndex < mStrings.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mStrings[aIndex] = aString; + } + + void RemoveItem(uint32_t aIndex) { + MOZ_ASSERT(aIndex < mStrings.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mStrings.RemoveElementAt(aIndex); + } + + bool AppendItem(const nsAString& aString) { + if (mStrings.AppendElement(aString, fallible)) { + mIsSet = true; + return true; + } + return false; + } + + protected: + /* See SVGLengthList for the rationale for using FallibleTArray instead + * of FallibleTArray. + */ + FallibleTArray mStrings; + bool mIsSet; + bool mIsCommaSeparated; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGSTRINGLIST_H_ diff --git a/dom/svg/SVGStyleElement.cpp b/dom/svg/SVGStyleElement.cpp new file mode 100644 index 0000000000..09b5994900 --- /dev/null +++ b/dom/svg/SVGStyleElement.cpp @@ -0,0 +1,210 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGStyleElement.h" + +#include "mozilla/RefPtr.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ReferrerInfo.h" +#include "mozilla/dom/SVGStyleElementBinding.h" +#include "nsCOMPtr.h" +#include "nsContentUtils.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Style) + +namespace mozilla::dom { + +JSObject* SVGStyleElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGStyleElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(SVGStyleElement, + SVGStyleElementBase, + nsIMutationObserver) + +NS_IMPL_CYCLE_COLLECTION_CLASS(SVGStyleElement) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGStyleElement, + SVGStyleElementBase) + tmp->LinkStyle::Traverse(cb); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGStyleElement, + SVGStyleElementBase) + tmp->LinkStyle::Unlink(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +//---------------------------------------------------------------------- +// Implementation + +SVGStyleElement::SVGStyleElement( + already_AddRefed&& aNodeInfo) + : SVGStyleElementBase(std::move(aNodeInfo)) { + AddMutationObserver(this); +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGStyleElement) + +//---------------------------------------------------------------------- +// nsIContent methods + +nsresult SVGStyleElement::BindToTree(BindContext& aContext, nsINode& aParent) { + nsresult rv = SVGStyleElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + void (SVGStyleElement::*update)() = + &SVGStyleElement::UpdateStyleSheetInternal; + nsContentUtils::AddScriptRunner( + NewRunnableMethod("dom::SVGStyleElement::BindToTree", this, update)); + + return rv; +} + +void SVGStyleElement::UnbindFromTree(bool aNullParent) { + nsCOMPtr oldDoc = GetUncomposedDoc(); + ShadowRoot* oldShadow = GetContainingShadow(); + SVGStyleElementBase::UnbindFromTree(aNullParent); + Unused << UpdateStyleSheetInternal(oldDoc, oldShadow); +} + +void SVGStyleElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aMaybeScriptedPrincipal, + bool aNotify) { + if (aNameSpaceID == kNameSpaceID_None) { + if (aName == nsGkAtoms::title || aName == nsGkAtoms::media || + aName == nsGkAtoms::type) { + Unused << UpdateStyleSheetInternal(nullptr, nullptr, ForceUpdate::Yes); + } + } + + return SVGStyleElementBase::AfterSetAttr( + aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify); +} + +bool SVGStyleElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) { + if (aNamespaceID == kNameSpaceID_None && + aAttribute == nsGkAtoms::crossorigin) { + ParseCORSValue(aValue, aResult); + return true; + } + + return SVGStyleElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue, + aMaybeScriptedPrincipal, aResult); +} + +//---------------------------------------------------------------------- +// nsIMutationObserver methods + +void SVGStyleElement::CharacterDataChanged(nsIContent* aContent, + const CharacterDataChangeInfo&) { + ContentChanged(aContent); +} + +void SVGStyleElement::ContentAppended(nsIContent* aFirstNewContent) { + ContentChanged(aFirstNewContent->GetParent()); +} + +void SVGStyleElement::ContentInserted(nsIContent* aChild) { + ContentChanged(aChild); +} + +void SVGStyleElement::ContentRemoved(nsIContent* aChild, + nsIContent* aPreviousSibling) { + ContentChanged(aChild); +} + +void SVGStyleElement::ContentChanged(nsIContent* aContent) { + if (nsContentUtils::IsInSameAnonymousTree(this, aContent)) { + Unused << UpdateStyleSheetInternal(nullptr, nullptr); + } +} + +//---------------------------------------------------------------------- + +void SVGStyleElement::GetMedia(nsAString& aMedia) { + GetAttr(nsGkAtoms::media, aMedia); +} + +void SVGStyleElement::SetMedia(const nsAString& aMedia, ErrorResult& rv) { + SetAttr(nsGkAtoms::media, aMedia, rv); +} + +void SVGStyleElement::GetType(nsAString& aType) { + GetAttr(nsGkAtoms::type, aType); +} + +void SVGStyleElement::SetType(const nsAString& aType, ErrorResult& rv) { + SetAttr(nsGkAtoms::type, aType, rv); +} + +void SVGStyleElement::GetTitle(nsAString& aTitle) { + GetAttr(nsGkAtoms::title, aTitle); +} + +void SVGStyleElement::SetTitle(const nsAString& aTitle, ErrorResult& rv) { + SetAttr(nsGkAtoms::title, aTitle, rv); +} + +bool SVGStyleElement::Disabled() const { + StyleSheet* ss = GetSheet(); + return ss && ss->Disabled(); +} + +void SVGStyleElement::SetDisabled(bool aDisabled) { + if (StyleSheet* ss = GetSheet()) { + ss->SetDisabled(aDisabled); + } +} + +//---------------------------------------------------------------------- +// nsStyleLinkElement methods + +Maybe SVGStyleElement::GetStyleSheetInfo() { + if (!IsCSSMimeTypeAttributeForStyleElement(*this)) { + return Nothing(); + } + + nsAutoString title; + nsAutoString media; + GetTitleAndMediaForElement(*this, title, media); + + return Some(SheetInfo{ + *OwnerDoc(), + this, + nullptr, + // FIXME(bug 1459822): Why doesn't this need a principal, but + // HTMLStyleElement does? + nullptr, + // FIXME(bug 1459822): Why does this need a crossorigin attribute, but + // HTMLStyleElement doesn't? + MakeAndAddRef(*this), + AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin)), + title, + media, + /* integrity = */ u""_ns, + /* nsStyleUtil::CSPAllowsInlineStyle takes care of nonce checking for + inline styles. Bug 1607011 */ + /* nonce = */ u""_ns, + HasAlternateRel::No, + IsInline::Yes, + IsExplicitlyEnabled::No, + }); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGStyleElement.h b/dom/svg/SVGStyleElement.h new file mode 100644 index 0000000000..b1b1850add --- /dev/null +++ b/dom/svg/SVGStyleElement.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGSTYLEELEMENT_H_ +#define DOM_SVG_SVGSTYLEELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/dom/LinkStyle.h" +#include "SVGElement.h" +#include "nsStubMutationObserver.h" + +nsresult NS_NewSVGStyleElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGStyleElementBase = SVGElement; + +class SVGStyleElement final : public SVGStyleElementBase, + public nsStubMutationObserver, + public LinkStyle { + protected: + friend nsresult(::NS_NewSVGStyleElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGStyleElement( + already_AddRefed&& aNodeInfo); + ~SVGStyleElement() = default; + + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SVGStyleElement, SVGStyleElementBase) + + // nsIContent + nsresult BindToTree(BindContext&, nsINode& aParent) override; + void UnbindFromTree(bool aNullParent = true) override; + virtual void AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aMaybeScriptedPrincipal, + bool aNotify) override; + virtual bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) override; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + // nsIMutationObserver + NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + + // WebIDL + bool Disabled() const; + void SetDisabled(bool aDisabled); + void GetMedia(nsAString& aMedia); + void SetMedia(const nsAString& aMedia, ErrorResult& rv); + void GetType(nsAString& aType); + void SetType(const nsAString& aType, ErrorResult& rv); + void GetTitle(nsAString& aTitle); + void SetTitle(const nsAString& aTitle, ErrorResult& rv); + + protected: + // Dummy init method to make the NS_IMPL_NS_NEW_SVG_ELEMENT and + // NS_IMPL_ELEMENT_CLONE_WITH_INIT usable with this class. This should be + // completely optimized away. + inline nsresult Init() { return NS_OK; } + + // LinkStyle overrides + nsIContent& AsContent() final { return *this; } + const LinkStyle* AsLinkStyle() const final { return this; } + Maybe GetStyleSheetInfo() final; + + /** + * Common method to call from the various mutation observer methods. + * aContent is a content node that's either the one that changed or its + * parent; we should only respond to the change if aContent is non-anonymous. + */ + void ContentChanged(nsIContent* aContent); +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGSTYLEELEMENT_H_ diff --git a/dom/svg/SVGSwitchElement.cpp b/dom/svg/SVGSwitchElement.cpp new file mode 100644 index 0000000000..e049d07496 --- /dev/null +++ b/dom/svg/SVGSwitchElement.cpp @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGSwitchElement.h" + +#include "nsLayoutUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/dom/SVGSwitchElementBinding.h" + +class nsIFrame; + +NS_IMPL_NS_NEW_SVG_ELEMENT(Switch) + +namespace mozilla::dom { + +JSObject* SVGSwitchElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGSwitchElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_CYCLE_COLLECTION_INHERITED(SVGSwitchElement, SVGSwitchElementBase, + mActiveChild) + +NS_IMPL_ADDREF_INHERITED(SVGSwitchElement, SVGSwitchElementBase) +NS_IMPL_RELEASE_INHERITED(SVGSwitchElement, SVGSwitchElementBase) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGSwitchElement) +NS_INTERFACE_MAP_END_INHERITING(SVGSwitchElementBase) + +//---------------------------------------------------------------------- +// Implementation + +SVGSwitchElement::SVGSwitchElement( + already_AddRefed&& aNodeInfo) + : SVGSwitchElementBase(std::move(aNodeInfo)) {} + +void SVGSwitchElement::MaybeInvalidate() { + // We must not change mActiveChild until after + // InvalidateAndScheduleBoundsUpdate has been called, otherwise + // it will not correctly invalidate the old mActiveChild area. + + nsIContent* newActiveChild = FindActiveChild(); + + if (newActiveChild == mActiveChild) { + return; + } + + nsIFrame* frame = GetPrimaryFrame(); + if (frame) { + nsLayoutUtils::PostRestyleEvent(this, RestyleHint{0}, + nsChangeHint_InvalidateRenderingObservers); + SVGUtils::ScheduleReflowSVG(frame); + } + + mActiveChild = newActiveChild; +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGSwitchElement) + +//---------------------------------------------------------------------- +// nsINode methods + +void SVGSwitchElement::InsertChildBefore(nsIContent* aKid, + nsIContent* aBeforeThis, bool aNotify, + ErrorResult& aRv) { + SVGSwitchElementBase::InsertChildBefore(aKid, aBeforeThis, aNotify, aRv); + if (aRv.Failed()) { + return; + } + + MaybeInvalidate(); +} + +void SVGSwitchElement::RemoveChildNode(nsIContent* aKid, bool aNotify) { + SVGSwitchElementBase::RemoveChildNode(aKid, aNotify); + MaybeInvalidate(); +} + +//---------------------------------------------------------------------- +// Implementation Helpers: + +nsIContent* SVGSwitchElement::FindActiveChild() const { + nsAutoString acceptLangs; + Preferences::GetLocalizedString("intl.accept_languages", acceptLangs); + + int32_t bestLanguagePreferenceRank = -1; + nsIContent* bestChild = nullptr; + nsIContent* defaultChild = nullptr; + for (nsIContent* child = nsINode::GetFirstChild(); child; + child = child->GetNextSibling()) { + if (!child->IsElement()) { + continue; + } + nsCOMPtr tests(do_QueryInterface(child)); + if (tests) { + if (tests->PassesConditionalProcessingTestsIgnoringSystemLanguage()) { + int32_t languagePreferenceRank = + tests->GetBestLanguagePreferenceRank(acceptLangs); + switch (languagePreferenceRank) { + case 0: + // best possible match + return child; + case -1: + // no match + break; + case -2: + // no systemLanguage attribute. If there's nothing better + // we'll use the first such child. + if (!defaultChild) { + defaultChild = child; + } + break; + default: + if (bestLanguagePreferenceRank == -1 || + languagePreferenceRank < bestLanguagePreferenceRank) { + bestLanguagePreferenceRank = languagePreferenceRank; + bestChild = child; + } + break; + } + } + } else if (!bestChild) { + bestChild = child; + } + } + return bestChild ? bestChild : defaultChild; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGSwitchElement.h b/dom/svg/SVGSwitchElement.h new file mode 100644 index 0000000000..c244c0fe51 --- /dev/null +++ b/dom/svg/SVGSwitchElement.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGSWITCHELEMENT_H_ +#define DOM_SVG_SVGSWITCHELEMENT_H_ + +#include "mozilla/dom/SVGGraphicsElement.h" +#include "nsCOMPtr.h" + +nsresult NS_NewSVGSwitchElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla { +class ErrorResult; +namespace dom { + +using SVGSwitchElementBase = SVGGraphicsElement; + +class SVGSwitchElement final : public SVGSwitchElementBase { + protected: + friend nsresult(::NS_NewSVGSwitchElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGSwitchElement( + already_AddRefed&& aNodeInfo); + ~SVGSwitchElement() = default; + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + NS_IMPL_FROMNODE_WITH_TAG(SVGSwitchElement, kNameSpaceID_SVG, svgSwitch) + + nsIContent* GetActiveChild() const { return mActiveChild; } + void MaybeInvalidate(); + + // interfaces: + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SVGSwitchElement, + SVGSwitchElementBase) + // nsINode + virtual void InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis, + bool aNotify, ErrorResult& aRv) override; + void RemoveChildNode(nsIContent* aKid, bool aNotify) override; + + // nsIContent + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + private: + void UpdateActiveChild() { mActiveChild = FindActiveChild(); } + nsIContent* FindActiveChild() const; + + // only this child will be displayed + nsCOMPtr mActiveChild; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGSWITCHELEMENT_H_ diff --git a/dom/svg/SVGSymbolElement.cpp b/dom/svg/SVGSymbolElement.cpp new file mode 100644 index 0000000000..d1e18b8923 --- /dev/null +++ b/dom/svg/SVGSymbolElement.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGSymbolElement.h" +#include "mozilla/dom/SVGSymbolElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Symbol) + +namespace mozilla::dom { + +JSObject* SVGSymbolElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGSymbolElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ISUPPORTS_INHERITED(SVGSymbolElement, SVGSymbolElementBase, + mozilla::dom::SVGTests) + +//---------------------------------------------------------------------- +// Implementation + +SVGSymbolElement::SVGSymbolElement( + already_AddRefed&& aNodeInfo) + : SVGSymbolElementBase(std::move(aNodeInfo)) {} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGSymbolElement) + +} // namespace mozilla::dom diff --git a/dom/svg/SVGSymbolElement.h b/dom/svg/SVGSymbolElement.h new file mode 100644 index 0000000000..2e2b1558f6 --- /dev/null +++ b/dom/svg/SVGSymbolElement.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGSYMBOLELEMENT_H_ +#define DOM_SVG_SVGSYMBOLELEMENT_H_ + +#include "SVGViewportElement.h" + +nsresult NS_NewSVGSymbolElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGSymbolElementBase = SVGViewportElement; + +class SVGSymbolElement final : public SVGSymbolElementBase { + protected: + friend nsresult(::NS_NewSVGSymbolElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGSymbolElement( + already_AddRefed&& aNodeInfo); + ~SVGSymbolElement() = default; + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + + public: + // interfaces: + NS_DECL_ISUPPORTS_INHERITED + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGSYMBOLELEMENT_H_ diff --git a/dom/svg/SVGTSpanElement.cpp b/dom/svg/SVGTSpanElement.cpp new file mode 100644 index 0000000000..36bb5c4c01 --- /dev/null +++ b/dom/svg/SVGTSpanElement.cpp @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGTSpanElement.h" +#include "mozilla/dom/SVGTSpanElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(TSpan) + +namespace mozilla::dom { + +JSObject* SVGTSpanElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGTSpanElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGTSpanElement::SVGTSpanElement( + already_AddRefed&& aNodeInfo) + : SVGTSpanElementBase(std::move(aNodeInfo)) {} + +SVGElement::EnumAttributesInfo SVGTSpanElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::LengthAttributesInfo SVGTSpanElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGTSpanElement) + +} // namespace mozilla::dom diff --git a/dom/svg/SVGTSpanElement.h b/dom/svg/SVGTSpanElement.h new file mode 100644 index 0000000000..a7dafc2c15 --- /dev/null +++ b/dom/svg/SVGTSpanElement.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTSPANELEMENT_H_ +#define DOM_SVG_SVGTSPANELEMENT_H_ + +#include "mozilla/dom/SVGTextPositioningElement.h" + +nsresult NS_NewSVGTSpanElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGTSpanElementBase = SVGTextPositioningElement; + +class SVGTSpanElement final : public SVGTSpanElementBase { + protected: + friend nsresult(::NS_NewSVGTSpanElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGTSpanElement( + already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + + public: + // nsIContent interface + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + protected: + EnumAttributesInfo GetEnumInfo() override; + LengthAttributesInfo GetLengthInfo() override; + + SVGAnimatedEnumeration mEnumAttributes[1]; + SVGAnimatedEnumeration* EnumAttributes() override { return mEnumAttributes; } + + SVGAnimatedLength mLengthAttributes[1]; + SVGAnimatedLength* LengthAttributes() override { return mLengthAttributes; } +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGTSPANELEMENT_H_ diff --git a/dom/svg/SVGTagList.h b/dom/svg/SVGTagList.h new file mode 100644 index 0000000000..6fb5c0bef8 --- /dev/null +++ b/dom/svg/SVGTagList.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/****** + + This file contains the list of all SVG tags. + + It is designed to be used as inline input to SVGElementFactory.cpp + through the magic of C preprocessing. + + Additionally, it is consumed by the self-regeneration code in + ElementName.java from which nsHtml5ElementName.cpp/h is translated. + See parser/html/java/README.txt. + + If you edit this list, you need to re-run ElementName.java + self-regeneration and the HTML parser Java to C++ translation. + + All entries must be enclosed in the macro SVG_TAG or SVG_FROM_PARSER_TAG + which will have cruel and unusual things done to them. + + SVG_FROM_PARSER_TAG is used where the element creation method takes + a FromParser argument, and SVG_TAG where it does not. + + It is recommended (but not strictly necessary) to keep all entries + in alphabetical order. + + The first argument to SVG_TAG is both the enum identifier of the + property and the atom name. The second argument is the "creator" + method of the form NS_New$TAGNAMEElement, that will be used by + SVGElementFactory.cpp to create a content object for a tag of that + type. + + ******/ + +SVG_TAG(a, A) +SVG_TAG(animate, Animate) +SVG_TAG(animateMotion, AnimateMotion) +SVG_TAG(animateTransform, AnimateTransform) +SVG_TAG(circle, Circle) +SVG_TAG(clipPath, ClipPath) +SVG_TAG(defs, Defs) +SVG_TAG(desc, Desc) +SVG_TAG(ellipse, Ellipse) +SVG_TAG(feBlend, FEBlend) +SVG_TAG(feColorMatrix, FEColorMatrix) +SVG_TAG(feComponentTransfer, FEComponentTransfer) +SVG_TAG(feComposite, FEComposite) +SVG_TAG(feConvolveMatrix, FEConvolveMatrix) +SVG_TAG(feDiffuseLighting, FEDiffuseLighting) +SVG_TAG(feDisplacementMap, FEDisplacementMap) +SVG_TAG(feDistantLight, FEDistantLight) +SVG_TAG(feDropShadow, FEDropShadow) +SVG_TAG(feFlood, FEFlood) +SVG_TAG(feFuncA, FEFuncA) +SVG_TAG(feFuncB, FEFuncB) +SVG_TAG(feFuncG, FEFuncG) +SVG_TAG(feFuncR, FEFuncR) +SVG_TAG(feGaussianBlur, FEGaussianBlur) +SVG_TAG(feImage, FEImage) +SVG_TAG(feMerge, FEMerge) +SVG_TAG(feMergeNode, FEMergeNode) +SVG_TAG(feMorphology, FEMorphology) +SVG_TAG(feOffset, FEOffset) +SVG_TAG(fePointLight, FEPointLight) +SVG_TAG(feSpecularLighting, FESpecularLighting) +SVG_TAG(feSpotLight, FESpotLight) +SVG_TAG(feTile, FETile) +SVG_TAG(feTurbulence, FETurbulence) +SVG_TAG(filter, Filter) +SVG_TAG(foreignObject, ForeignObject) +SVG_TAG(g, G) +SVG_TAG(image, Image) +SVG_TAG(line, Line) +SVG_TAG(linearGradient, LinearGradient) +SVG_TAG(marker, Marker) +SVG_TAG(mask, Mask) +SVG_TAG(metadata, Metadata) +SVG_TAG(mpath, MPath) +SVG_TAG(path, Path) +SVG_TAG(pattern, Pattern) +SVG_TAG(polygon, Polygon) +SVG_TAG(polyline, Polyline) +SVG_TAG(radialGradient, RadialGradient) +SVG_TAG(rect, Rect) +SVG_FROM_PARSER_TAG(script, Script) +SVG_TAG(set, Set) +SVG_TAG(stop, Stop) +SVG_TAG(style, Style) +SVG_FROM_PARSER_TAG(svg, SVG) +SVG_TAG(svgSwitch, Switch) +SVG_TAG(symbol, Symbol) +SVG_TAG(text, Text) +SVG_TAG(textPath, TextPath) +SVG_TAG(title, Title) +SVG_TAG(tspan, TSpan) +SVG_TAG(use, Use) +SVG_TAG(view, View) diff --git a/dom/svg/SVGTests.cpp b/dom/svg/SVGTests.cpp new file mode 100644 index 0000000000..6603663445 --- /dev/null +++ b/dom/svg/SVGTests.cpp @@ -0,0 +1,201 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGTests.h" +#include "DOMSVGStringList.h" +#include "nsIContent.h" +#include "nsIContentInlines.h" +#include "mozilla/dom/SVGSwitchElement.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsStyleUtil.h" +#include "mozilla/Preferences.h" + +namespace mozilla::dom { + +nsStaticAtom* const SVGTests::sStringListNames[2] = { + nsGkAtoms::requiredExtensions, + nsGkAtoms::systemLanguage, +}; + +SVGTests::SVGTests() { + mStringListAttributes[LANGUAGE].SetIsCommaSeparated(true); +} + +already_AddRefed SVGTests::RequiredExtensions() { + return DOMSVGStringList::GetDOMWrapper(&mStringListAttributes[EXTENSIONS], + AsSVGElement(), true, EXTENSIONS); +} + +already_AddRefed SVGTests::SystemLanguage() { + return DOMSVGStringList::GetDOMWrapper(&mStringListAttributes[LANGUAGE], + AsSVGElement(), true, LANGUAGE); +} + +bool SVGTests::HasExtension(const nsAString& aExtension) const { +#define SVG_SUPPORTED_EXTENSION(str) \ + if (aExtension.EqualsLiteral(str)) return true; + SVG_SUPPORTED_EXTENSION("http://www.w3.org/1999/xhtml") + nsNameSpaceManager* nameSpaceManager = nsNameSpaceManager::GetInstance(); + if (AsSVGElement()->IsInChromeDocument() || + !nameSpaceManager->mMathMLDisabled) { + SVG_SUPPORTED_EXTENSION("http://www.w3.org/1998/Math/MathML") + } +#undef SVG_SUPPORTED_EXTENSION + + return false; +} + +bool SVGTests::IsConditionalProcessingAttribute( + const nsAtom* aAttribute) const { + for (uint32_t i = 0; i < ArrayLength(sStringListNames); i++) { + if (aAttribute == sStringListNames[i]) { + return true; + } + } + return false; +} + +int32_t SVGTests::GetBestLanguagePreferenceRank( + const nsAString& aAcceptLangs) const { + if (!mStringListAttributes[LANGUAGE].IsExplicitlySet()) { + return -2; + } + + int32_t lowestRank = -1; + + for (uint32_t i = 0; i < mStringListAttributes[LANGUAGE].Length(); i++) { + int32_t index = 0; + for (const nsAString& languageToken : + nsCharSeparatedTokenizer(aAcceptLangs, ',').ToRange()) { + bool exactMatch = languageToken.Equals(mStringListAttributes[LANGUAGE][i], + nsCaseInsensitiveStringComparator); + bool prefixOnlyMatch = + !exactMatch && nsStyleUtil::DashMatchCompare( + mStringListAttributes[LANGUAGE][i], languageToken, + nsCaseInsensitiveStringComparator); + if (index == 0 && exactMatch) { + // best possible match + return 0; + } + if ((exactMatch || prefixOnlyMatch) && + (lowestRank == -1 || 2 * index + prefixOnlyMatch < lowestRank)) { + lowestRank = 2 * index + prefixOnlyMatch; + } + ++index; + } + } + return lowestRank; +} + +bool SVGTests::PassesConditionalProcessingTestsIgnoringSystemLanguage() const { + // Required Extensions + // + // The requiredExtensions attribute defines a list of required language + // extensions. Language extensions are capabilities within a user agent that + // go beyond the feature set defined in the SVG specification. + // Each extension is identified by a URI reference. + // For now, claim that mozilla's SVG implementation supports XHTML and MathML. + if (mStringListAttributes[EXTENSIONS].IsExplicitlySet()) { + if (mStringListAttributes[EXTENSIONS].IsEmpty()) { + return false; + } + for (uint32_t i = 0; i < mStringListAttributes[EXTENSIONS].Length(); i++) { + if (!HasExtension(mStringListAttributes[EXTENSIONS][i])) { + return false; + } + } + } + return true; +} + +bool SVGTests::PassesConditionalProcessingTests() const { + if (!PassesConditionalProcessingTestsIgnoringSystemLanguage()) { + return false; + } + + // systemLanguage + // + // Evaluates to "true" if one of the languages indicated by user preferences + // exactly equals one of the languages given in the value of this parameter, + // or if one of the languages indicated by user preferences exactly equals a + // prefix of one of the languages given in the value of this parameter such + // that the first tag character following the prefix is "-". + if (mStringListAttributes[LANGUAGE].IsExplicitlySet()) { + if (mStringListAttributes[LANGUAGE].IsEmpty()) { + return false; + } + + // Get our language preferences + nsAutoString acceptLangs; + Preferences::GetLocalizedString("intl.accept_languages", acceptLangs); + + if (acceptLangs.IsEmpty()) { + NS_WARNING( + "no default language specified for systemLanguage conditional test"); + return false; + } + + for (uint32_t i = 0; i < mStringListAttributes[LANGUAGE].Length(); i++) { + nsCharSeparatedTokenizer languageTokenizer(acceptLangs, ','); + while (languageTokenizer.hasMoreTokens()) { + if (nsStyleUtil::DashMatchCompare(mStringListAttributes[LANGUAGE][i], + languageTokenizer.nextToken(), + nsCaseInsensitiveStringComparator)) { + return true; + } + } + } + return false; + } + + return true; +} + +bool SVGTests::ParseConditionalProcessingAttribute(nsAtom* aAttribute, + const nsAString& aValue, + nsAttrValue& aResult) { + for (uint32_t i = 0; i < ArrayLength(sStringListNames); i++) { + if (aAttribute == sStringListNames[i]) { + nsresult rv = mStringListAttributes[i].SetValue(aValue); + if (NS_FAILED(rv)) { + mStringListAttributes[i].Clear(); + } + MaybeInvalidate(); + return true; + } + } + return false; +} + +void SVGTests::UnsetAttr(const nsAtom* aAttribute) { + for (uint32_t i = 0; i < ArrayLength(sStringListNames); i++) { + if (aAttribute == sStringListNames[i]) { + mStringListAttributes[i].Clear(); + MaybeInvalidate(); + return; + } + } +} + +nsStaticAtom* SVGTests::GetAttrName(uint8_t aAttrEnum) const { + return sStringListNames[aAttrEnum]; +} + +void SVGTests::GetAttrValue(uint8_t aAttrEnum, nsAttrValue& aValue) const { + MOZ_ASSERT(aAttrEnum < ArrayLength(sStringListNames), + "aAttrEnum out of range"); + aValue.SetTo(mStringListAttributes[aAttrEnum], nullptr); +} + +void SVGTests::MaybeInvalidate() { + nsIContent* parent = AsSVGElement()->GetFlattenedTreeParent(); + + if (auto* svgSwitch = SVGSwitchElement::FromNodeOrNull(parent)) { + svgSwitch->MaybeInvalidate(); + } +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGTests.h b/dom/svg/SVGTests.h new file mode 100644 index 0000000000..148c35fc5c --- /dev/null +++ b/dom/svg/SVGTests.h @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTESTS_H_ +#define DOM_SVG_SVGTESTS_H_ + +#include "nsStringFwd.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/SVGStringList.h" + +class nsAttrValue; +class nsAtom; +class nsStaticAtom; + +namespace mozilla { + +namespace dom { +class DOMSVGStringList; +} + +#define MOZILLA_DOMSVGTESTS_IID \ + { \ + 0x92370da8, 0xda28, 0x4895, { \ + 0x9b, 0x1b, 0xe0, 0x06, 0x0d, 0xb7, 0x3f, 0xc3 \ + } \ + } + +namespace dom { + +class SVGElement; + +class SVGTests : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOMSVGTESTS_IID) + + SVGTests(); + + friend class dom::DOMSVGStringList; + using SVGStringList = mozilla::SVGStringList; + + /** + * Compare the language name(s) in a systemLanguage attribute to the + * user's language preferences, as defined in + * http://www.w3.org/TR/SVG11/struct.html#SystemLanguageAttribute + * We have a match if a language name in the users language preferences + * exactly equals one of the language names or exactly equals a prefix of + * one of the language names in the systemLanguage attribute. + * @returns 2 * the lowest index in the aAcceptLangs that matches + 1 + * if only the prefix matches, -2 if there's no systemLanguage attribute, + * or -1 if no indices match. + * XXX This algorithm is O(M*N). + */ + int32_t GetBestLanguagePreferenceRank(const nsAString& aAcceptLangs) const; + + /** + * Check whether the conditional processing attributes other than + * systemLanguage "return true" if they apply to and are specified + * on the given element. Returns true if this element should be + * rendered, false if it should not. + */ + bool PassesConditionalProcessingTestsIgnoringSystemLanguage() const; + + /** + * Check whether the conditional processing attributes requiredExtensions + * and systemLanguage both "return true" if they apply to + * and are specified on the given element. Returns true if this element + * should be rendered, false if it should not. + */ + bool PassesConditionalProcessingTests() const; + + /** + * Check whether the conditional processing attributes requiredExtensions + * and systemLanguage both "return true" if they apply to + * and are specified on the given element. Returns true if this element + * should be rendered, false if it should not. + * + * @param aAcceptLangs The value of the intl.accept_languages preference + */ + bool PassesConditionalProcessingTests(const nsAString& aAcceptLangs) const; + + /** + * Returns true if the attribute is one of the conditional processing + * attributes. + */ + bool IsConditionalProcessingAttribute(const nsAtom* aAttribute) const; + + bool ParseConditionalProcessingAttribute(nsAtom* aAttribute, + const nsAString& aValue, + nsAttrValue& aResult); + + /** + * Unsets a conditional processing attribute. + */ + void UnsetAttr(const nsAtom* aAttribute); + + nsStaticAtom* GetAttrName(uint8_t aAttrEnum) const; + void GetAttrValue(uint8_t aAttrEnum, nsAttrValue& aValue) const; + + void MaybeInvalidate(); + + // WebIDL + already_AddRefed RequiredExtensions(); + already_AddRefed SystemLanguage(); + + bool HasExtension(const nsAString& aExtension) const; + + virtual SVGElement* AsSVGElement() = 0; + + const SVGElement* AsSVGElement() const { + return const_cast(this)->AsSVGElement(); + } + + protected: + virtual ~SVGTests() = default; + + private: + enum { EXTENSIONS, LANGUAGE }; + SVGStringList mStringListAttributes[2]; + static nsStaticAtom* const sStringListNames[2]; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(SVGTests, MOZILLA_DOMSVGTESTS_IID) + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGTESTS_H_ diff --git a/dom/svg/SVGTextContentElement.cpp b/dom/svg/SVGTextContentElement.cpp new file mode 100644 index 0000000000..4015bdf017 --- /dev/null +++ b/dom/svg/SVGTextContentElement.cpp @@ -0,0 +1,203 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGTextContentElement.h" + +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGTextContentElementBinding.h" +#include "mozilla/dom/SVGRect.h" +#include "nsBidiUtils.h" +#include "DOMSVGPoint.h" +#include "nsLayoutUtils.h" +#include "nsTextFragment.h" +#include "nsTextFrameUtils.h" +#include "nsTextNode.h" +#include "SVGTextFrame.h" + +namespace mozilla::dom { + +using namespace SVGTextContentElement_Binding; + +SVGEnumMapping SVGTextContentElement::sLengthAdjustMap[] = { + {nsGkAtoms::spacing, LENGTHADJUST_SPACING}, + {nsGkAtoms::spacingAndGlyphs, LENGTHADJUST_SPACINGANDGLYPHS}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGTextContentElement::sEnumInfo[1] = { + {nsGkAtoms::lengthAdjust, sLengthAdjustMap, LENGTHADJUST_SPACING}}; + +SVGElement::LengthInfo SVGTextContentElement::sLengthInfo[1] = { + {nsGkAtoms::textLength, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::XY}}; + +SVGTextFrame* SVGTextContentElement::GetSVGTextFrame() { + nsIFrame* frame = GetPrimaryFrame(FlushType::Layout); + nsIFrame* textFrame = + nsLayoutUtils::GetClosestFrameOfType(frame, LayoutFrameType::SVGText); + return static_cast(textFrame); +} + +SVGTextFrame* +SVGTextContentElement::GetSVGTextFrameForNonLayoutDependentQuery() { + nsIFrame* frame = GetPrimaryFrame(FlushType::Frames); + nsIFrame* textFrame = + nsLayoutUtils::GetClosestFrameOfType(frame, LayoutFrameType::SVGText); + return static_cast(textFrame); +} + +already_AddRefed SVGTextContentElement::TextLength() { + return LengthAttributes()[TEXTLENGTH].ToDOMAnimatedLength(this); +} + +already_AddRefed +SVGTextContentElement::LengthAdjust() { + return EnumAttributes()[LENGTHADJUST].ToDOMAnimatedEnum(this); +} + +//---------------------------------------------------------------------- + +template +static bool FragmentHasSkippableCharacter(const T* aBuffer, uint32_t aLength) { + for (uint32_t i = 0; i < aLength; i++) { + if (nsTextFrameUtils::IsSkippableCharacterForTransformText(aBuffer[i])) { + return true; + } + } + return false; +} + +Maybe SVGTextContentElement::GetNonLayoutDependentNumberOfChars() { + SVGTextFrame* frame = GetSVGTextFrameForNonLayoutDependentQuery(); + if (!frame || frame != GetPrimaryFrame()) { + // Only support this fast path on , not child s, etc. + return Nothing(); + } + + uint32_t num = 0; + + for (nsINode* n = Element::GetFirstChild(); n; n = n->GetNextSibling()) { + if (!n->IsText()) { + return Nothing(); + } + + const nsTextFragment* text = &n->AsText()->TextFragment(); + uint32_t length = text->GetLength(); + + if (text->Is2b()) { + if (FragmentHasSkippableCharacter(text->Get2b(), length)) { + return Nothing(); + } + } else { + auto buffer = reinterpret_cast(text->Get1b()); + if (FragmentHasSkippableCharacter(buffer, length)) { + return Nothing(); + } + } + + num += length; + } + + return Some(num); +} + +int32_t SVGTextContentElement::GetNumberOfChars() { + Maybe num = GetNonLayoutDependentNumberOfChars(); + if (num) { + return *num; + } + + SVGTextFrame* textFrame = GetSVGTextFrame(); + return textFrame ? textFrame->GetNumberOfChars(this) : 0; +} + +float SVGTextContentElement::GetComputedTextLength() { + SVGTextFrame* textFrame = GetSVGTextFrame(); + return textFrame ? textFrame->GetComputedTextLength(this) : 0.0f; +} + +void SVGTextContentElement::SelectSubString(uint32_t charnum, uint32_t nchars, + ErrorResult& rv) { + SVGTextFrame* textFrame = GetSVGTextFrame(); + if (!textFrame) return; + + textFrame->SelectSubString(this, charnum, nchars, rv); +} + +float SVGTextContentElement::GetSubStringLength(uint32_t charnum, + uint32_t nchars, + ErrorResult& rv) { + SVGTextFrame* textFrame = GetSVGTextFrameForNonLayoutDependentQuery(); + if (!textFrame) return 0.0f; + + if (!textFrame->RequiresSlowFallbackForSubStringLength()) { + return textFrame->GetSubStringLengthFastPath(this, charnum, nchars, rv); + } + // We need to make sure that we've been reflowed before using the slow + // fallback path as it may affect glyph positioning. GetSVGTextFrame will do + // that for us. + // XXX perf: It may be possible to limit reflow to just calling ReflowSVG, + // but we would still need to resort to full reflow for percentage + // positioning attributes. For now we just do a full reflow regardless + // since the cases that would cause us to be called are relatively uncommon. + textFrame = GetSVGTextFrame(); + if (!textFrame) return 0.0f; + + return textFrame->GetSubStringLengthSlowFallback(this, charnum, nchars, rv); +} + +already_AddRefed SVGTextContentElement::GetStartPositionOfChar( + uint32_t charnum, ErrorResult& rv) { + SVGTextFrame* textFrame = GetSVGTextFrame(); + if (!textFrame) { + rv.ThrowInvalidStateError("No layout information available for SVG text"); + return nullptr; + } + + return textFrame->GetStartPositionOfChar(this, charnum, rv); +} + +already_AddRefed SVGTextContentElement::GetEndPositionOfChar( + uint32_t charnum, ErrorResult& rv) { + SVGTextFrame* textFrame = GetSVGTextFrame(); + if (!textFrame) { + rv.ThrowInvalidStateError("No layout information available for SVG text"); + return nullptr; + } + + return textFrame->GetEndPositionOfChar(this, charnum, rv); +} + +already_AddRefed SVGTextContentElement::GetExtentOfChar( + uint32_t charnum, ErrorResult& rv) { + SVGTextFrame* textFrame = GetSVGTextFrame(); + + if (!textFrame) { + rv.ThrowInvalidStateError("No layout information available for SVG text"); + return nullptr; + } + + return textFrame->GetExtentOfChar(this, charnum, rv); +} + +float SVGTextContentElement::GetRotationOfChar(uint32_t charnum, + ErrorResult& rv) { + SVGTextFrame* textFrame = GetSVGTextFrame(); + + if (!textFrame) { + rv.ThrowInvalidStateError("No layout information available for SVG text"); + return 0.0f; + } + + return textFrame->GetRotationOfChar(this, charnum, rv); +} + +int32_t SVGTextContentElement::GetCharNumAtPosition( + const DOMPointInit& aPoint) { + SVGTextFrame* textFrame = GetSVGTextFrame(); + return textFrame ? textFrame->GetCharNumAtPosition(this, aPoint) : -1; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGTextContentElement.h b/dom/svg/SVGTextContentElement.h new file mode 100644 index 0000000000..697c016a41 --- /dev/null +++ b/dom/svg/SVGTextContentElement.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTEXTCONTENTELEMENT_H_ +#define DOM_SVG_SVGTEXTCONTENTELEMENT_H_ + +#include "mozilla/dom/SVGGraphicsElement.h" +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedLength.h" + +namespace mozilla { + +class SVGTextFrame; + +namespace dom { + +struct DOMPointInit; +class DOMSVGAnimatedEnumeration; +class DOMSVGPoint; +class SVGRect; + +using SVGTextContentElementBase = SVGGraphicsElement; + +class SVGTextContentElement : public SVGTextContentElementBase { + friend class mozilla::SVGTextFrame; + + public: + using FragmentOrElement::TextLength; + + // WebIDL + already_AddRefed TextLength(); + already_AddRefed LengthAdjust(); + MOZ_CAN_RUN_SCRIPT int32_t GetNumberOfChars(); + MOZ_CAN_RUN_SCRIPT float GetComputedTextLength(); + MOZ_CAN_RUN_SCRIPT + void SelectSubString(uint32_t charnum, uint32_t nchars, ErrorResult& rv); + MOZ_CAN_RUN_SCRIPT + float GetSubStringLength(uint32_t charnum, uint32_t nchars, ErrorResult& rv); + MOZ_CAN_RUN_SCRIPT + already_AddRefed GetStartPositionOfChar(uint32_t charnum, + ErrorResult& rv); + MOZ_CAN_RUN_SCRIPT + already_AddRefed GetEndPositionOfChar(uint32_t charnum, + ErrorResult& rv); + MOZ_CAN_RUN_SCRIPT + already_AddRefed GetExtentOfChar(uint32_t charnum, ErrorResult& rv); + MOZ_CAN_RUN_SCRIPT float GetRotationOfChar(uint32_t charnum, ErrorResult& rv); + MOZ_CAN_RUN_SCRIPT int32_t GetCharNumAtPosition(const DOMPointInit& aPoint); + + protected: + explicit SVGTextContentElement( + already_AddRefed&& aNodeInfo) + : SVGTextContentElementBase(std::move(aNodeInfo)) {} + + MOZ_CAN_RUN_SCRIPT SVGTextFrame* GetSVGTextFrame(); + MOZ_CAN_RUN_SCRIPT SVGTextFrame* GetSVGTextFrameForNonLayoutDependentQuery(); + MOZ_CAN_RUN_SCRIPT mozilla::Maybe + GetNonLayoutDependentNumberOfChars(); + + enum { LENGTHADJUST }; + virtual SVGAnimatedEnumeration* EnumAttributes() = 0; + static SVGEnumMapping sLengthAdjustMap[]; + static EnumInfo sEnumInfo[1]; + + enum { TEXTLENGTH }; + virtual SVGAnimatedLength* LengthAttributes() = 0; + static LengthInfo sLengthInfo[1]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGTEXTCONTENTELEMENT_H_ diff --git a/dom/svg/SVGTextElement.cpp b/dom/svg/SVGTextElement.cpp new file mode 100644 index 0000000000..ca02d93cc2 --- /dev/null +++ b/dom/svg/SVGTextElement.cpp @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGTextElement.h" +#include "mozilla/dom/SVGTextElementBinding.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Text) + +namespace mozilla::dom { + +JSObject* SVGTextElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGTextElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// Implementation + +SVGTextElement::SVGTextElement( + already_AddRefed&& aNodeInfo) + : SVGTextElementBase(std::move(aNodeInfo)) {} + +SVGElement::EnumAttributesInfo SVGTextElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::LengthAttributesInfo SVGTextElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGTextElement) + +} // namespace mozilla::dom diff --git a/dom/svg/SVGTextElement.h b/dom/svg/SVGTextElement.h new file mode 100644 index 0000000000..3450e62c2c --- /dev/null +++ b/dom/svg/SVGTextElement.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTEXTELEMENT_H_ +#define DOM_SVG_SVGTEXTELEMENT_H_ + +#include "mozilla/dom/SVGTextPositioningElement.h" + +nsresult NS_NewSVGTextElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +using SVGTextElementBase = SVGTextPositioningElement; + +class SVGTextElement final : public SVGTextElementBase { + protected: + explicit SVGTextElement(already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + + friend nsresult(::NS_NewSVGTextElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + + public: + // nsIContent interface + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + protected: + EnumAttributesInfo GetEnumInfo() override; + LengthAttributesInfo GetLengthInfo() override; + + SVGAnimatedEnumeration mEnumAttributes[1]; + SVGAnimatedEnumeration* EnumAttributes() override { return mEnumAttributes; } + + SVGAnimatedLength mLengthAttributes[1]; + SVGAnimatedLength* LengthAttributes() override { return mLengthAttributes; } +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGTEXTELEMENT_H_ diff --git a/dom/svg/SVGTextPathElement.cpp b/dom/svg/SVGTextPathElement.cpp new file mode 100644 index 0000000000..05b87f0e68 --- /dev/null +++ b/dom/svg/SVGTextPathElement.cpp @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGTextPathElement.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGTextContentElementBinding.h" +#include "mozilla/dom/SVGTextPathElementBinding.h" +#include "SVGElement.h" +#include "nsGkAtoms.h" +#include "nsError.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(TextPath) + +namespace mozilla::dom { + +using namespace SVGTextContentElement_Binding; +using namespace SVGTextPathElement_Binding; + +class DOMSVGAnimatedLength; + +JSObject* SVGTextPathElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGTextPathElement_Binding::Wrap(aCx, this, aGivenProto); +} + +SVGElement::LengthInfo SVGTextPathElement::sLengthInfo[2] = { + // from SVGTextContentElement: + {nsGkAtoms::textLength, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::XY}, + // from SVGTextPathElement: + {nsGkAtoms::startOffset, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}}; + +SVGEnumMapping SVGTextPathElement::sMethodMap[] = { + {nsGkAtoms::align, TEXTPATH_METHODTYPE_ALIGN}, + {nsGkAtoms::stretch, TEXTPATH_METHODTYPE_STRETCH}, + {nullptr, 0}}; + +SVGEnumMapping SVGTextPathElement::sSpacingMap[] = { + {nsGkAtoms::_auto, TEXTPATH_SPACINGTYPE_AUTO}, + {nsGkAtoms::exact, TEXTPATH_SPACINGTYPE_EXACT}, + {nullptr, 0}}; + +SVGEnumMapping SVGTextPathElement::sSideMap[] = { + {nsGkAtoms::left, TEXTPATH_SIDETYPE_LEFT}, + {nsGkAtoms::right, TEXTPATH_SIDETYPE_RIGHT}, + {nullptr, 0}}; + +SVGElement::EnumInfo SVGTextPathElement::sEnumInfo[4] = { + // from SVGTextContentElement: + {nsGkAtoms::lengthAdjust, sLengthAdjustMap, LENGTHADJUST_SPACING}, + // from SVGTextPathElement: + {nsGkAtoms::method, sMethodMap, TEXTPATH_METHODTYPE_ALIGN}, + {nsGkAtoms::spacing, sSpacingMap, TEXTPATH_SPACINGTYPE_EXACT}, + {nsGkAtoms::side_, sSideMap, TEXTPATH_SIDETYPE_LEFT}}; + +SVGElement::StringInfo SVGTextPathElement::sStringInfo[2] = { + {nsGkAtoms::href, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_XLink, true}}; + +//---------------------------------------------------------------------- +// Implementation + +SVGTextPathElement::SVGTextPathElement( + already_AddRefed&& aNodeInfo) + : SVGTextPathElementBase(std::move(aNodeInfo)) {} + +void SVGTextPathElement::HrefAsString(nsAString& aHref) { + if (mStringAttributes[SVGTextPathElement::HREF].IsExplicitlySet()) { + mStringAttributes[SVGTextPathElement::HREF].GetAnimValue(aHref, this); + } else { + mStringAttributes[SVGTextPathElement::XLINK_HREF].GetAnimValue(aHref, this); + } +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGTextPathElement) + +already_AddRefed SVGTextPathElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- + +already_AddRefed SVGTextPathElement::StartOffset() { + return mLengthAttributes[STARTOFFSET].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGTextPathElement::Method() { + return mEnumAttributes[METHOD].ToDOMAnimatedEnum(this); +} + +already_AddRefed SVGTextPathElement::Spacing() { + return mEnumAttributes[SPACING].ToDOMAnimatedEnum(this); +} + +already_AddRefed SVGTextPathElement::Side() { + return mEnumAttributes[SIDE].ToDOMAnimatedEnum(this); +} + +//---------------------------------------------------------------------- +// SVGElement overrides + +SVGElement::LengthAttributesInfo SVGTextPathElement::GetLengthInfo() { + return LengthAttributesInfo(mLengthAttributes, sLengthInfo, + ArrayLength(sLengthInfo)); +} + +SVGElement::EnumAttributesInfo SVGTextPathElement::GetEnumInfo() { + return EnumAttributesInfo(mEnumAttributes, sEnumInfo, ArrayLength(sEnumInfo)); +} + +SVGElement::StringAttributesInfo SVGTextPathElement::GetStringInfo() { + return StringAttributesInfo(mStringAttributes, sStringInfo, + ArrayLength(sStringInfo)); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGTextPathElement.h b/dom/svg/SVGTextPathElement.h new file mode 100644 index 0000000000..8af7ceed25 --- /dev/null +++ b/dom/svg/SVGTextPathElement.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTEXTPATHELEMENT_H_ +#define DOM_SVG_SVGTEXTPATHELEMENT_H_ + +#include "SVGAnimatedEnumeration.h" +#include "SVGAnimatedLength.h" +#include "SVGAnimatedPathSegList.h" +#include "SVGAnimatedString.h" +#include "mozilla/dom/SVGTextContentElement.h" + +class nsAtom; +class nsIContent; + +nsresult NS_NewSVGTextPathElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); + +namespace mozilla::dom { + +// textPath side types +static const uint16_t TEXTPATH_SIDETYPE_LEFT = 1; +static const uint16_t TEXTPATH_SIDETYPE_RIGHT = 2; + +using SVGTextPathElementBase = SVGTextContentElement; + +class SVGTextPathElement final : public SVGTextPathElementBase { + friend class mozilla::SVGTextFrame; + + protected: + friend nsresult(::NS_NewSVGTextPathElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGTextPathElement( + already_AddRefed&& aNodeInfo); + JSObject* WrapNode(JSContext* cx, JS::Handle aGivenProto) override; + + public: + // nsIContent interface + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + SVGAnimatedPathSegList* GetAnimPathSegList() override { return &mPath; } + + nsStaticAtom* GetPathDataAttrName() const override { return nsGkAtoms::path; } + + // WebIDL + already_AddRefed StartOffset(); + already_AddRefed Method(); + already_AddRefed Spacing(); + already_AddRefed Side(); + already_AddRefed Href(); + + void HrefAsString(nsAString& aHref); + + protected: + LengthAttributesInfo GetLengthInfo() override; + EnumAttributesInfo GetEnumInfo() override; + StringAttributesInfo GetStringInfo() override; + + enum { /* TEXTLENGTH, */ STARTOFFSET = 1 }; + SVGAnimatedLength mLengthAttributes[2]; + SVGAnimatedLength* LengthAttributes() override { return mLengthAttributes; } + static LengthInfo sLengthInfo[2]; + + enum { /* LENGTHADJUST, */ METHOD = 1, SPACING, SIDE }; + SVGAnimatedEnumeration mEnumAttributes[4]; + SVGAnimatedEnumeration* EnumAttributes() override { return mEnumAttributes; } + static SVGEnumMapping sMethodMap[]; + static SVGEnumMapping sSpacingMap[]; + static SVGEnumMapping sSideMap[]; + static EnumInfo sEnumInfo[4]; + + enum { HREF, XLINK_HREF }; + SVGAnimatedString mStringAttributes[2]; + static StringInfo sStringInfo[2]; + + SVGAnimatedPathSegList mPath; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGTEXTPATHELEMENT_H_ diff --git a/dom/svg/SVGTextPositioningElement.cpp b/dom/svg/SVGTextPositioningElement.cpp new file mode 100644 index 0000000000..7baa060e30 --- /dev/null +++ b/dom/svg/SVGTextPositioningElement.cpp @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGTextPositioningElement.h" + +#include "mozilla/ArrayUtils.h" +#include "SVGAnimatedLengthList.h" +#include "DOMSVGAnimatedLengthList.h" +#include "DOMSVGAnimatedNumberList.h" +#include "SVGContentUtils.h" + +namespace mozilla::dom { + +SVGElement::LengthListInfo SVGTextPositioningElement::sLengthListInfo[4] = { + {nsGkAtoms::x, SVGContentUtils::X, false}, + {nsGkAtoms::y, SVGContentUtils::Y, false}, + {nsGkAtoms::dx, SVGContentUtils::X, true}, + {nsGkAtoms::dy, SVGContentUtils::Y, true}}; + +SVGElement::LengthListAttributesInfo +SVGTextPositioningElement::GetLengthListInfo() { + return LengthListAttributesInfo(mLengthListAttributes, sLengthListInfo, + ArrayLength(sLengthListInfo)); +} + +SVGElement::NumberListInfo SVGTextPositioningElement::sNumberListInfo[1] = { + {nsGkAtoms::rotate}}; + +SVGElement::NumberListAttributesInfo +SVGTextPositioningElement::GetNumberListInfo() { + return NumberListAttributesInfo(mNumberListAttributes, sNumberListInfo, + ArrayLength(sNumberListInfo)); +} + +//---------------------------------------------------------------------- + +already_AddRefed SVGTextPositioningElement::X() { + return DOMSVGAnimatedLengthList::GetDOMWrapper( + &mLengthListAttributes[ATTR_X], this, ATTR_X, SVGContentUtils::X); +} + +already_AddRefed SVGTextPositioningElement::Y() { + return DOMSVGAnimatedLengthList::GetDOMWrapper( + &mLengthListAttributes[ATTR_Y], this, ATTR_Y, SVGContentUtils::Y); +} + +already_AddRefed SVGTextPositioningElement::Dx() { + return DOMSVGAnimatedLengthList::GetDOMWrapper( + &mLengthListAttributes[ATTR_DX], this, ATTR_DX, SVGContentUtils::X); +} + +already_AddRefed SVGTextPositioningElement::Dy() { + return DOMSVGAnimatedLengthList::GetDOMWrapper( + &mLengthListAttributes[ATTR_DY], this, ATTR_DY, SVGContentUtils::Y); +} + +already_AddRefed SVGTextPositioningElement::Rotate() { + return DOMSVGAnimatedNumberList::GetDOMWrapper(&mNumberListAttributes[ROTATE], + this, ROTATE); +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGTextPositioningElement.h b/dom/svg/SVGTextPositioningElement.h new file mode 100644 index 0000000000..a7b4b4b73d --- /dev/null +++ b/dom/svg/SVGTextPositioningElement.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTEXTPOSITIONINGELEMENT_H_ +#define DOM_SVG_SVGTEXTPOSITIONINGELEMENT_H_ + +#include "mozilla/dom/SVGTextContentElement.h" +#include "SVGAnimatedLengthList.h" +#include "SVGAnimatedNumberList.h" + +namespace mozilla { +class SVGAnimatedLengthList; + +namespace dom { +class DOMSVGAnimatedLengthList; +class DOMSVGAnimatedNumberList; +using SVGTextPositioningElementBase = SVGTextContentElement; + +class SVGTextPositioningElement : public SVGTextPositioningElementBase { + public: + // WebIDL + already_AddRefed X(); + already_AddRefed Y(); + already_AddRefed Dx(); + already_AddRefed Dy(); + already_AddRefed Rotate(); + + protected: + explicit SVGTextPositioningElement( + already_AddRefed&& aNodeInfo) + : SVGTextPositioningElementBase(std::move(aNodeInfo)) {} + + LengthListAttributesInfo GetLengthListInfo() override; + NumberListAttributesInfo GetNumberListInfo() override; + + enum { ATTR_X, ATTR_Y, ATTR_DX, ATTR_DY }; + SVGAnimatedLengthList mLengthListAttributes[4]; + static LengthListInfo sLengthListInfo[4]; + + enum { ROTATE }; + SVGAnimatedNumberList mNumberListAttributes[1]; + static NumberListInfo sNumberListInfo[1]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // DOM_SVG_SVGTEXTPOSITIONINGELEMENT_H_ diff --git a/dom/svg/SVGTitleElement.cpp b/dom/svg/SVGTitleElement.cpp new file mode 100644 index 0000000000..d56fc44934 --- /dev/null +++ b/dom/svg/SVGTitleElement.cpp @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGTitleElement.h" +#include "mozilla/dom/SVGTitleElementBinding.h" + +#include "mozilla/dom/Document.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Title) + +namespace mozilla::dom { + +JSObject* SVGTitleElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGTitleElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_ISUPPORTS_INHERITED(SVGTitleElement, SVGTitleElementBase, + nsIMutationObserver) + +//---------------------------------------------------------------------- +// Implementation + +SVGTitleElement::SVGTitleElement( + already_AddRefed&& aNodeInfo) + : SVGTitleElementBase(std::move(aNodeInfo)) { + AddMutationObserver(this); +} + +void SVGTitleElement::CharacterDataChanged(nsIContent* aContent, + const CharacterDataChangeInfo&) { + SendTitleChangeEvent(false); +} + +void SVGTitleElement::ContentAppended(nsIContent* aFirstNewContent) { + SendTitleChangeEvent(false); +} + +void SVGTitleElement::ContentInserted(nsIContent* aChild) { + SendTitleChangeEvent(false); +} + +void SVGTitleElement::ContentRemoved(nsIContent* aChild, + nsIContent* aPreviousSibling) { + SendTitleChangeEvent(false); +} + +nsresult SVGTitleElement::BindToTree(BindContext& aContext, nsINode& aParent) { + // Let this fall through. + nsresult rv = SVGTitleElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + SendTitleChangeEvent(true); + + return NS_OK; +} + +void SVGTitleElement::UnbindFromTree(bool aNullParent) { + SendTitleChangeEvent(false); + + // Let this fall through. + SVGTitleElementBase::UnbindFromTree(aNullParent); +} + +void SVGTitleElement::DoneAddingChildren(bool aHaveNotified) { + if (!aHaveNotified) { + SendTitleChangeEvent(false); + } +} + +void SVGTitleElement::SendTitleChangeEvent(bool aBound) { + Document* doc = GetUncomposedDoc(); + if (doc) { + doc->NotifyPossibleTitleChange(aBound); + } +} + +//---------------------------------------------------------------------- +// nsINode methods + +NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGTitleElement) + +} // namespace mozilla::dom diff --git a/dom/svg/SVGTitleElement.h b/dom/svg/SVGTitleElement.h new file mode 100644 index 0000000000..52f8bf886c --- /dev/null +++ b/dom/svg/SVGTitleElement.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTITLEELEMENT_H_ +#define DOM_SVG_SVGTITLEELEMENT_H_ + +#include "mozilla/Attributes.h" +#include "SVGElement.h" +#include "nsStubMutationObserver.h" + +nsresult NS_NewSVGTitleElement( + nsIContent** aResult, already_AddRefed&& aNodeInfo); +namespace mozilla::dom { + +using SVGTitleElementBase = SVGElement; + +class SVGTitleElement final : public SVGTitleElementBase, + public nsStubMutationObserver { + protected: + friend nsresult(::NS_NewSVGTitleElement( + nsIContent** aResult, + already_AddRefed&& aNodeInfo)); + explicit SVGTitleElement( + already_AddRefed&& aNodeInfo); + ~SVGTitleElement() = default; + + JSObject* WrapNode(JSContext* aCx, + JS::Handle aGivenProto) override; + + public: + // interfaces: + + NS_DECL_ISUPPORTS_INHERITED + + // nsIMutationObserver + NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; + + nsresult BindToTree(BindContext&, nsINode& aParent) override; + + void UnbindFromTree(bool aNullParent = true) override; + + void DoneAddingChildren(bool aHaveNotified) override; + + private: + void SendTitleChangeEvent(bool aBound); +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGTITLEELEMENT_H_ diff --git a/dom/svg/SVGTransform.cpp b/dom/svg/SVGTransform.cpp new file mode 100644 index 0000000000..c993253c4c --- /dev/null +++ b/dom/svg/SVGTransform.cpp @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGTransform.h" + +#include "nsError.h" +#include "nsContentUtils.h" // for NS_ENSURE_FINITE +#include "nsTextFormatter.h" + +namespace { +const double kRadPerDegree = 2.0 * M_PI / 360.0; +} // namespace + +namespace mozilla { + +using namespace dom::SVGTransform_Binding; + +void SVGTransform::GetValueAsString(nsAString& aValue) const { + switch (mType) { + case SVG_TRANSFORM_TRANSLATE: + // The spec say that if Y is not provided, it is assumed to be zero. + if (mMatrix._32 != 0) + nsTextFormatter::ssprintf(aValue, u"translate(%g, %g)", mMatrix._31, + mMatrix._32); + else + nsTextFormatter::ssprintf(aValue, u"translate(%g)", mMatrix._31); + break; + case SVG_TRANSFORM_ROTATE: + if (mOriginX != 0.0f || mOriginY != 0.0f) + nsTextFormatter::ssprintf(aValue, u"rotate(%g, %g, %g)", mAngle, + mOriginX, mOriginY); + else + nsTextFormatter::ssprintf(aValue, u"rotate(%g)", mAngle); + break; + case SVG_TRANSFORM_SCALE: + if (mMatrix._11 != mMatrix._22) + nsTextFormatter::ssprintf(aValue, u"scale(%g, %g)", mMatrix._11, + mMatrix._22); + else + nsTextFormatter::ssprintf(aValue, u"scale(%g)", mMatrix._11); + break; + case SVG_TRANSFORM_SKEWX: + nsTextFormatter::ssprintf(aValue, u"skewX(%g)", mAngle); + break; + case SVG_TRANSFORM_SKEWY: + nsTextFormatter::ssprintf(aValue, u"skewY(%g)", mAngle); + break; + case SVG_TRANSFORM_MATRIX: + nsTextFormatter::ssprintf(aValue, u"matrix(%g, %g, %g, %g, %g, %g)", + mMatrix._11, mMatrix._12, mMatrix._21, + mMatrix._22, mMatrix._31, mMatrix._32); + break; + default: + aValue.Truncate(); + NS_ERROR("unknown transformation type"); + break; + } +} + +void SVGTransform::SetMatrix(const gfxMatrix& aMatrix) { + mType = SVG_TRANSFORM_MATRIX; + mMatrix = aMatrix; + // We set the other members here too, since operator== requires it and + // the DOM requires it for mAngle. + mAngle = 0.f; + mOriginX = 0.f; + mOriginY = 0.f; +} + +void SVGTransform::SetTranslate(float aTx, float aTy) { + mType = SVG_TRANSFORM_TRANSLATE; + mMatrix = gfxMatrix::Translation(aTx, aTy); + mAngle = 0.f; + mOriginX = 0.f; + mOriginY = 0.f; +} + +void SVGTransform::SetScale(float aSx, float aSy) { + mType = SVG_TRANSFORM_SCALE; + mMatrix = gfxMatrix::Scaling(aSx, aSy); + mAngle = 0.f; + mOriginX = 0.f; + mOriginY = 0.f; +} + +void SVGTransform::SetRotate(float aAngle, float aCx, float aCy) { + mType = SVG_TRANSFORM_ROTATE; + mMatrix = gfxMatrix::Translation(aCx, aCy) + .PreRotate(aAngle * kRadPerDegree) + .PreTranslate(-aCx, -aCy); + mAngle = aAngle; + mOriginX = aCx; + mOriginY = aCy; +} + +nsresult SVGTransform::SetSkewX(float aAngle) { + double ta = tan(aAngle * kRadPerDegree); + // No one actually cares about the exact error return type here. + NS_ENSURE_FINITE(ta, NS_ERROR_INVALID_ARG); + + mType = SVG_TRANSFORM_SKEWX; + mMatrix = gfxMatrix(); + mMatrix._21 = ta; + mAngle = aAngle; + mOriginX = 0.f; + mOriginY = 0.f; + return NS_OK; +} + +nsresult SVGTransform::SetSkewY(float aAngle) { + double ta = tan(aAngle * kRadPerDegree); + // No one actually cares about the exact error return type here. + NS_ENSURE_FINITE(ta, NS_ERROR_INVALID_ARG); + + mType = SVG_TRANSFORM_SKEWY; + mMatrix = gfxMatrix(); + mMatrix._12 = ta; + mAngle = aAngle; + mOriginX = 0.f; + mOriginY = 0.f; + return NS_OK; +} + +SVGTransformSMILData::SVGTransformSMILData(const SVGTransform& aTransform) + : mTransformType(aTransform.Type()) { + MOZ_ASSERT(mTransformType >= SVG_TRANSFORM_MATRIX && + mTransformType <= SVG_TRANSFORM_SKEWY, + "Unexpected transform type"); + + for (uint32_t i = 0; i < NUM_STORED_PARAMS; ++i) { + mParams[i] = 0.f; + } + + switch (mTransformType) { + case SVG_TRANSFORM_MATRIX: { + const gfxMatrix& mx = aTransform.GetMatrix(); + mParams[0] = static_cast(mx._11); + mParams[1] = static_cast(mx._12); + mParams[2] = static_cast(mx._21); + mParams[3] = static_cast(mx._22); + mParams[4] = static_cast(mx._31); + mParams[5] = static_cast(mx._32); + break; + } + case SVG_TRANSFORM_TRANSLATE: { + const gfxMatrix& mx = aTransform.GetMatrix(); + mParams[0] = static_cast(mx._31); + mParams[1] = static_cast(mx._32); + break; + } + case SVG_TRANSFORM_SCALE: { + const gfxMatrix& mx = aTransform.GetMatrix(); + mParams[0] = static_cast(mx._11); + mParams[1] = static_cast(mx._22); + break; + } + case SVG_TRANSFORM_ROTATE: + mParams[0] = aTransform.Angle(); + aTransform.GetRotationOrigin(mParams[1], mParams[2]); + break; + + case SVG_TRANSFORM_SKEWX: + case SVG_TRANSFORM_SKEWY: + mParams[0] = aTransform.Angle(); + break; + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected transform type"); + break; + } +} + +SVGTransform SVGTransformSMILData::ToSVGTransform() const { + SVGTransform result; + + switch (mTransformType) { + case SVG_TRANSFORM_MATRIX: + result.SetMatrix(gfxMatrix(mParams[0], mParams[1], mParams[2], mParams[3], + mParams[4], mParams[5])); + break; + + case SVG_TRANSFORM_TRANSLATE: + result.SetTranslate(mParams[0], mParams[1]); + break; + + case SVG_TRANSFORM_SCALE: + result.SetScale(mParams[0], mParams[1]); + break; + + case SVG_TRANSFORM_ROTATE: + result.SetRotate(mParams[0], mParams[1], mParams[2]); + break; + + case SVG_TRANSFORM_SKEWX: + result.SetSkewX(mParams[0]); + break; + + case SVG_TRANSFORM_SKEWY: + result.SetSkewY(mParams[0]); + break; + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected transform type"); + break; + } + return result; +} + +} // namespace mozilla diff --git a/dom/svg/SVGTransform.h b/dom/svg/SVGTransform.h new file mode 100644 index 0000000000..50a330c854 --- /dev/null +++ b/dom/svg/SVGTransform.h @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTRANSFORM_H_ +#define DOM_SVG_SVGTRANSFORM_H_ + +#include "gfxMatrix.h" +#include "mozilla/dom/SVGTransformBinding.h" +#include "mozilla/gfx/Matrix.h" +#include "nsDebug.h" + +namespace mozilla { + +/* + * The DOM wrapper class for this class is DOMSVGTransform. + */ +class SVGTransform { + public: + // Default ctor initialises to matrix type with identity matrix + SVGTransform() + : mMatrix() // Initialises to identity + , + mAngle(0.f), + mOriginX(0.f), + mOriginY(0.f), + mType(dom::SVGTransform_Binding::SVG_TRANSFORM_MATRIX) {} + + explicit SVGTransform(const gfxMatrix& aMatrix) + : mMatrix(aMatrix), + mAngle(0.f), + mOriginX(0.f), + mOriginY(0.f), + mType(dom::SVGTransform_Binding::SVG_TRANSFORM_MATRIX) {} + + bool operator==(const SVGTransform& rhs) const { + return mType == rhs.mType && MatricesEqual(mMatrix, rhs.mMatrix) && + mAngle == rhs.mAngle && mOriginX == rhs.mOriginX && + mOriginY == rhs.mOriginY; + } + + void GetValueAsString(nsAString& aValue) const; + + float Angle() const { return mAngle; } + void GetRotationOrigin(float& aOriginX, float& aOriginY) const { + aOriginX = mOriginX; + aOriginY = mOriginY; + } + uint16_t Type() const { return mType; } + + const gfxMatrix& GetMatrix() const { return mMatrix; } + void SetMatrix(const gfxMatrix& aMatrix); + void SetTranslate(float aTx, float aTy); + void SetScale(float aSx, float aSy); + void SetRotate(float aAngle, float aCx, float aCy); + nsresult SetSkewX(float aAngle); + nsresult SetSkewY(float aAngle); + + static bool MatricesEqual(const gfxMatrix& a, const gfxMatrix& b) { + return a._11 == b._11 && a._12 == b._12 && a._21 == b._21 && + a._22 == b._22 && a._31 == b._31 && a._32 == b._32; + } + + protected: + gfxMatrix mMatrix; + float mAngle, mOriginX, mOriginY; + uint16_t mType; +}; + +/* + * A slightly more light-weight version of SVGTransform for SMIL animation. + * + * Storing the parameters in an array (rather than a matrix) also allows simpler + * (transform type-agnostic) interpolation and addition. + * + * The meaning of the mParams array depends on the transform type as follows: + * + * Type | mParams[0], mParams[1], mParams[2], ... + * --------------------+----------------------------------------- + * translate | tx, ty + * scale | sx, sy + * rotate | rotation-angle (in degrees), cx, cy + * skewX | skew-angle (in degrees) + * skewY | skew-angle (in degrees) + * matrix | a, b, c, d, e, f + * + * The matrix type is never generated by animation code (it is only produced + * when the user inserts one via the DOM) and often requires special handling + * when we do encounter it. Therefore many users of this class are only + * interested in the first three parameters and so we provide a special + * constructor for setting those parameters only. + */ +class SVGTransformSMILData { + public: + // Number of float-params required in constructor, if constructing one of the + // 'simple' transform types (all but matrix type) + static const uint32_t NUM_SIMPLE_PARAMS = 3; + + // Number of float-params required in constructor for matrix type. + // This is also the number of params we actually store, regardless of type. + static const uint32_t NUM_STORED_PARAMS = 6; + + explicit SVGTransformSMILData(uint16_t aType) : mTransformType(aType) { + MOZ_ASSERT(aType >= dom::SVGTransform_Binding::SVG_TRANSFORM_MATRIX && + aType <= dom::SVGTransform_Binding::SVG_TRANSFORM_SKEWY, + "Unexpected transform type"); + for (uint32_t i = 0; i < NUM_STORED_PARAMS; ++i) { + mParams[i] = 0.f; + } + } + + SVGTransformSMILData(uint16_t aType, float (&aParams)[NUM_SIMPLE_PARAMS]) + : mTransformType(aType) { + MOZ_ASSERT(aType >= dom::SVGTransform_Binding::SVG_TRANSFORM_TRANSLATE && + aType <= dom::SVGTransform_Binding::SVG_TRANSFORM_SKEWY, + "Expected 'simple' transform type"); + for (uint32_t i = 0; i < NUM_SIMPLE_PARAMS; ++i) { + mParams[i] = aParams[i]; + } + for (uint32_t i = NUM_SIMPLE_PARAMS; i < NUM_STORED_PARAMS; ++i) { + mParams[i] = 0.f; + } + } + + // Conversion to/from a fully-fledged SVGTransform + explicit SVGTransformSMILData(const SVGTransform& aTransform); + SVGTransform ToSVGTransform() const; + + bool operator==(const SVGTransformSMILData& aOther) const { + if (mTransformType != aOther.mTransformType) return false; + + for (uint32_t i = 0; i < NUM_STORED_PARAMS; ++i) { + if (mParams[i] != aOther.mParams[i]) { + return false; + } + } + + return true; + } + + bool operator!=(const SVGTransformSMILData& aOther) const { + return !(*this == aOther); + } + + uint16_t mTransformType; + float mParams[NUM_STORED_PARAMS]; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGTRANSFORM_H_ diff --git a/dom/svg/SVGTransformList.cpp b/dom/svg/SVGTransformList.cpp new file mode 100644 index 0000000000..244ca61c85 --- /dev/null +++ b/dom/svg/SVGTransformList.cpp @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGTransformList.h" +#include "SVGTransformListParser.h" +#include "nsString.h" +#include "nsError.h" + +namespace mozilla { + +gfxMatrix SVGTransformList::GetConsolidationMatrix() const { + // To benefit from Return Value Optimization and avoid copy constructor calls + // due to our use of return-by-value, we must return the exact same object + // from ALL return points. This function must only return THIS variable: + gfxMatrix result; + + if (mItems.IsEmpty()) return result; + + result = mItems[0].GetMatrix(); + + if (mItems.Length() == 1) return result; + + for (uint32_t i = 1; i < mItems.Length(); ++i) { + result.PreMultiply(mItems[i].GetMatrix()); + } + + return result; +} + +nsresult SVGTransformList::CopyFrom(const SVGTransformList& rhs) { + return CopyFrom(rhs.mItems); +} + +nsresult SVGTransformList::CopyFrom( + const nsTArray& aTransformArray) { + if (!mItems.Assign(aTransformArray, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +void SVGTransformList::GetValueAsString(nsAString& aValue) const { + aValue.Truncate(); + uint32_t last = mItems.Length() - 1; + for (uint32_t i = 0; i < mItems.Length(); ++i) { + nsAutoString length; + mItems[i].GetValueAsString(length); + // We ignore OOM, since it's not useful for us to return an error. + aValue.Append(length); + if (i != last) { + aValue.Append(' '); + } + } +} + +nsresult SVGTransformList::SetValueFromString(const nsAString& aValue) { + SVGTransformListParser parser(aValue); + if (!parser.Parse()) { + // there was a parse error. + return NS_ERROR_DOM_SYNTAX_ERR; + } + + return CopyFrom(parser.GetTransformList()); +} + +} // namespace mozilla diff --git a/dom/svg/SVGTransformList.h b/dom/svg/SVGTransformList.h new file mode 100644 index 0000000000..55aacf7007 --- /dev/null +++ b/dom/svg/SVGTransformList.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTRANSFORMLIST_H_ +#define DOM_SVG_SVGTRANSFORMLIST_H_ + +#include "gfxMatrix.h" +#include "mozilla/dom/SVGTransform.h" +#include "nsTArray.h" + +namespace mozilla { + +namespace dom { +class DOMSVGTransform; +class DOMSVGTransformList; +} // namespace dom + +/** + * ATTENTION! WARNING! WATCH OUT!! + * + * Consumers that modify objects of this type absolutely MUST keep the DOM + * wrappers for those lists (if any) in sync!! That's why this class is so + * locked down. + * + * The DOM wrapper class for this class is DOMSVGTransformList. + */ +class SVGTransformList { + friend class SVGAnimatedTransformList; + friend class dom::DOMSVGTransform; + friend class dom::DOMSVGTransformList; + + public: + SVGTransformList() = default; + ~SVGTransformList() = default; + + // Only methods that don't make/permit modification to this list are public. + // Only our friend classes can access methods that may change us. + + /// This may return an incomplete string on OOM, but that's acceptable. + void GetValueAsString(nsAString& aValue) const; + + bool IsEmpty() const { return mItems.IsEmpty(); } + + uint32_t Length() const { return mItems.Length(); } + + const SVGTransform& operator[](uint32_t aIndex) const { + return mItems[aIndex]; + } + + bool operator==(const SVGTransformList& rhs) const { + return mItems == rhs.mItems; + } + + bool SetCapacity(uint32_t size) { return mItems.SetCapacity(size, fallible); } + + void Compact() { mItems.Compact(); } + + gfxMatrix GetConsolidationMatrix() const; + + // Access to methods that can modify objects of this type is deliberately + // limited. This is to reduce the chances of someone modifying objects of + // this type without taking the necessary steps to keep DOM wrappers in sync. + // If you need wider access to these methods, consider adding a method to + // DOMSVGAnimatedTransformList and having that class act as an intermediary so + // it can take care of keeping DOM wrappers in sync. + + protected: + /** + * These may fail on OOM if the internal capacity needs to be increased, in + * which case the list will be left unmodified. + */ + nsresult CopyFrom(const SVGTransformList& rhs); + nsresult CopyFrom(const nsTArray& aTransformArray); + + SVGTransform& operator[](uint32_t aIndex) { return mItems[aIndex]; } + + /** + * This may fail (return false) on OOM if the internal capacity is being + * increased, in which case the list will be left unmodified. + */ + bool SetLength(uint32_t aNumberOfItems) { + return mItems.SetLength(aNumberOfItems, fallible); + } + + private: + // Marking the following private only serves to show which methods are only + // used by our friend classes (as opposed to our subclasses) - it doesn't + // really provide additional safety. + + nsresult SetValueFromString(const nsAString& aValue); + + void Clear() { mItems.Clear(); } + + bool InsertItem(uint32_t aIndex, const SVGTransform& aTransform) { + if (aIndex >= mItems.Length()) { + aIndex = mItems.Length(); + } + return !!mItems.InsertElementAt(aIndex, aTransform, fallible); + } + + void ReplaceItem(uint32_t aIndex, const SVGTransform& aTransform) { + MOZ_ASSERT(aIndex < mItems.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mItems[aIndex] = aTransform; + } + + void RemoveItem(uint32_t aIndex) { + MOZ_ASSERT(aIndex < mItems.Length(), + "DOM wrapper caller should have raised INDEX_SIZE_ERR"); + mItems.RemoveElementAt(aIndex); + } + + bool AppendItem(const SVGTransform& aTransform) { + return !!mItems.AppendElement(aTransform, fallible); + } + + protected: + /* + * See SVGLengthList for the rationale for using + * FallibleTArray instead of FallibleTArray. + */ + FallibleTArray mItems; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGTRANSFORMLIST_H_ diff --git a/dom/svg/SVGTransformListParser.cpp b/dom/svg/SVGTransformListParser.cpp new file mode 100644 index 0000000000..692577bea7 --- /dev/null +++ b/dom/svg/SVGTransformListParser.cpp @@ -0,0 +1,251 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGTransformListParser.h" + +#include "mozilla/ArrayUtils.h" +#include "SVGContentUtils.h" +#include "SVGTransform.h" +#include "nsGkAtoms.h" +#include "nsAtom.h" + +namespace mozilla { + +//---------------------------------------------------------------------- +// private methods + +bool SVGTransformListParser::Parse() { + mTransforms.Clear(); + return ParseTransforms(); +} + +bool SVGTransformListParser::ParseTransforms() { + if (!SkipWsp()) { + return true; + } + + if (!ParseTransform()) { + return false; + } + + while (SkipWsp()) { + // The SVG BNF allows multiple comma-wsp between transforms + while (*mIter == ',') { + ++mIter; + if (!SkipWsp()) { + return false; + } + } + + if (!ParseTransform()) { + return false; + } + } + return true; +} + +bool SVGTransformListParser::ParseTransform() { + RangedPtr start(mIter); + while (IsAlpha(*mIter)) { + ++mIter; + if (mIter == mEnd) { + return false; + } + } + + if (start == mIter) { + // Didn't read anything + return false; + } + + const nsAString& transform = Substring(start.get(), mIter.get()); + nsStaticAtom* keyAtom = NS_GetStaticAtom(transform); + + if (!keyAtom || !SkipWsp()) { + return false; + } + + if (keyAtom == nsGkAtoms::translate) { + return ParseTranslate(); + } + if (keyAtom == nsGkAtoms::scale) { + return ParseScale(); + } + if (keyAtom == nsGkAtoms::rotate) { + return ParseRotate(); + } + if (keyAtom == nsGkAtoms::skewX) { + return ParseSkewX(); + } + if (keyAtom == nsGkAtoms::skewY) { + return ParseSkewY(); + } + if (keyAtom == nsGkAtoms::matrix) { + return ParseMatrix(); + } + return false; +} + +bool SVGTransformListParser::ParseArguments(float* aResult, uint32_t aMaxCount, + uint32_t* aParsedCount) { + if (*mIter != '(') { + return false; + } + ++mIter; + + if (!SkipWsp()) { + return false; + } + + if (!SVGContentUtils::ParseNumber(mIter, mEnd, aResult[0])) { + return false; + } + *aParsedCount = 1; + + while (SkipWsp()) { + if (*mIter == ')') { + ++mIter; + return true; + } + if (*aParsedCount == aMaxCount) { + return false; + } + SkipCommaWsp(); + if (!SVGContentUtils::ParseNumber(mIter, mEnd, + aResult[(*aParsedCount)++])) { + return false; + } + } + return false; +} + +bool SVGTransformListParser::ParseTranslate() { + float t[2]; + uint32_t count; + + if (!ParseArguments(t, ArrayLength(t), &count)) { + return false; + } + + switch (count) { + case 1: + t[1] = 0.f; + [[fallthrough]]; + case 2: { + SVGTransform* transform = mTransforms.AppendElement(fallible); + if (!transform) { + return false; + } + transform->SetTranslate(t[0], t[1]); + return true; + } + } + + return false; +} + +bool SVGTransformListParser::ParseScale() { + float s[2]; + uint32_t count; + + if (!ParseArguments(s, ArrayLength(s), &count)) { + return false; + } + + switch (count) { + case 1: + s[1] = s[0]; + [[fallthrough]]; + case 2: { + SVGTransform* transform = mTransforms.AppendElement(fallible); + if (!transform) { + return false; + } + transform->SetScale(s[0], s[1]); + return true; + } + } + + return false; +} + +bool SVGTransformListParser::ParseRotate() { + float r[3]; + uint32_t count; + + if (!ParseArguments(r, ArrayLength(r), &count)) { + return false; + } + + switch (count) { + case 1: + r[1] = r[2] = 0.f; + [[fallthrough]]; + case 3: { + SVGTransform* transform = mTransforms.AppendElement(fallible); + if (!transform) { + return false; + } + transform->SetRotate(r[0], r[1], r[2]); + return true; + } + } + + return false; +} + +bool SVGTransformListParser::ParseSkewX() { + float skew; + uint32_t count; + + if (!ParseArguments(&skew, 1, &count) || count != 1) { + return false; + } + + SVGTransform* transform = mTransforms.AppendElement(fallible); + if (!transform) { + return false; + } + transform->SetSkewX(skew); + + return true; +} + +bool SVGTransformListParser::ParseSkewY() { + float skew; + uint32_t count; + + if (!ParseArguments(&skew, 1, &count) || count != 1) { + return false; + } + + SVGTransform* transform = mTransforms.AppendElement(fallible); + if (!transform) { + return false; + } + transform->SetSkewY(skew); + + return true; +} + +bool SVGTransformListParser::ParseMatrix() { + float m[6]; + uint32_t count; + + if (!ParseArguments(m, ArrayLength(m), &count) || count != 6) { + return false; + } + + SVGTransform* transform = mTransforms.AppendElement(fallible); + if (!transform) { + return false; + } + transform->SetMatrix(gfxMatrix(m[0], m[1], m[2], m[3], m[4], m[5])); + + return true; +} + +} // namespace mozilla diff --git a/dom/svg/SVGTransformListParser.h b/dom/svg/SVGTransformListParser.h new file mode 100644 index 0000000000..38e2e3ba5c --- /dev/null +++ b/dom/svg/SVGTransformListParser.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTRANSFORMLISTPARSER_H_ +#define DOM_SVG_SVGTRANSFORMLISTPARSER_H_ + +#include "mozilla/Attributes.h" +#include "SVGDataParser.h" +#include "nsTArray.h" + +//////////////////////////////////////////////////////////////////////// +// SVGTransformListParser: A simple recursive descent parser that builds +// transform lists from transform attributes. The grammar for path data +// can be found in SVG 1.1, chapter 7. +// http://www.w3.org/TR/SVG11/coords.html#TransformAttribute + +namespace mozilla { + +class SVGTransform; + +class SVGTransformListParser : public SVGDataParser { + public: + explicit SVGTransformListParser(const nsAString& aValue) + : SVGDataParser(aValue) {} + + bool Parse(); + + const nsTArray& GetTransformList() const { return mTransforms; } + + private: + // helpers + bool ParseArguments(float* aResult, uint32_t aMaxCount, + uint32_t* aParsedCount); + + bool ParseTransforms(); + + bool ParseTransform(); + + bool ParseTranslate(); + bool ParseScale(); + bool ParseRotate(); + bool ParseSkewX(); + bool ParseSkewY(); + bool ParseMatrix(); + + FallibleTArray mTransforms; +}; + +} // namespace mozilla + +#endif // DOM_SVG_SVGTRANSFORMLISTPARSER_H_ diff --git a/dom/svg/SVGTransformListSMILType.cpp b/dom/svg/SVGTransformListSMILType.cpp new file mode 100644 index 0000000000..e548f97bc7 --- /dev/null +++ b/dom/svg/SVGTransformListSMILType.cpp @@ -0,0 +1,343 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGTransformListSMILType.h" + +#include "mozilla/SMILValue.h" +#include "nsCRT.h" +#include "SVGTransformList.h" +#include "SVGTransform.h" +#include + +using namespace mozilla::dom::SVGTransform_Binding; + +namespace mozilla { + +using TransformArray = FallibleTArray; + +//---------------------------------------------------------------------- +// nsISMILType implementation + +void SVGTransformListSMILType::Init(SMILValue& aValue) const { + MOZ_ASSERT(aValue.IsNull(), "Unexpected value type"); + + TransformArray* transforms = new TransformArray(1); + aValue.mU.mPtr = transforms; + aValue.mType = this; +} + +void SVGTransformListSMILType::Destroy(SMILValue& aValue) const { + MOZ_ASSERT(aValue.mType == this, "Unexpected SMIL value type"); + TransformArray* params = static_cast(aValue.mU.mPtr); + delete params; + aValue.mU.mPtr = nullptr; + aValue.mType = SMILNullType::Singleton(); +} + +nsresult SVGTransformListSMILType::Assign(SMILValue& aDest, + const SMILValue& aSrc) const { + MOZ_ASSERT(aDest.mType == aSrc.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL value"); + + const TransformArray* srcTransforms = + static_cast(aSrc.mU.mPtr); + TransformArray* dstTransforms = static_cast(aDest.mU.mPtr); + if (!dstTransforms->Assign(*srcTransforms, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +bool SVGTransformListSMILType::IsEqual(const SMILValue& aLeft, + const SMILValue& aRight) const { + MOZ_ASSERT(aLeft.mType == aRight.mType, "Incompatible SMIL types"); + MOZ_ASSERT(aLeft.mType == this, "Unexpected SMIL type"); + + const TransformArray& leftArr( + *static_cast(aLeft.mU.mPtr)); + const TransformArray& rightArr( + *static_cast(aRight.mU.mPtr)); + + // If array-lengths don't match, we're trivially non-equal. + if (leftArr.Length() != rightArr.Length()) { + return false; + } + + // Array-lengths match -- check each array-entry for equality. + uint32_t length = leftArr.Length(); // == rightArr->Length(), if we get here + for (uint32_t i = 0; i < length; ++i) { + if (leftArr[i] != rightArr[i]) { + return false; + } + } + + // Found no differences. + return true; +} + +nsresult SVGTransformListSMILType::Add(SMILValue& aDest, + const SMILValue& aValueToAdd, + uint32_t aCount) const { + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aDest.mType == aValueToAdd.mType, "Incompatible SMIL types"); + + TransformArray& dstTransforms(*static_cast(aDest.mU.mPtr)); + const TransformArray& srcTransforms( + *static_cast(aValueToAdd.mU.mPtr)); + + // We're doing a simple add here (as opposed to a sandwich add below). + // We only do this when we're accumulating a repeat result or calculating + // a by-animation value. + // + // In either case we should have 1 transform in the source array. + NS_ASSERTION(srcTransforms.Length() == 1, + "Invalid source transform list to add"); + + // And we should have 0 or 1 transforms in the dest array. + // (We can have 0 transforms in the case of by-animation when we are + // calculating the by-value as "0 + by". Zero being represented by a + // SMILValue with an empty transform array.) + NS_ASSERTION(dstTransforms.Length() < 2, + "Invalid dest transform list to add to"); + + // Get the individual transforms to add + const SVGTransformSMILData& srcTransform = srcTransforms[0]; + if (dstTransforms.IsEmpty()) { + SVGTransformSMILData* result = dstTransforms.AppendElement( + SVGTransformSMILData(srcTransform.mTransformType), fallible); + NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY); + } + SVGTransformSMILData& dstTransform = dstTransforms[0]; + + // The types must be the same + NS_ASSERTION(srcTransform.mTransformType == dstTransform.mTransformType, + "Trying to perform simple add of different transform types"); + + // And it should be impossible that one of them is of matrix type + NS_ASSERTION(srcTransform.mTransformType != SVG_TRANSFORM_MATRIX, + "Trying to perform simple add with matrix transform"); + + // Add the parameters + for (int i = 0; i <= 2; ++i) { + dstTransform.mParams[i] += srcTransform.mParams[i] * aCount; + } + + return NS_OK; +} + +nsresult SVGTransformListSMILType::SandwichAdd( + SMILValue& aDest, const SMILValue& aValueToAdd) const { + MOZ_ASSERT(aDest.mType == this, "Unexpected SMIL type"); + MOZ_ASSERT(aDest.mType == aValueToAdd.mType, "Incompatible SMIL types"); + + // For a sandwich add means a matrix post-multiplication + // which just means to put the additional transform on the end of the array + + TransformArray& dstTransforms(*static_cast(aDest.mU.mPtr)); + const TransformArray& srcTransforms( + *static_cast(aValueToAdd.mU.mPtr)); + + // We should have 0 or 1 transforms in the src list. + NS_ASSERTION(srcTransforms.Length() < 2, + "Trying to do sandwich add of more than one value"); + + // The empty src transform list case only occurs in some limited circumstances + // where we create an empty 'from' value to interpolate from (e.g. + // by-animation) but then skip the interpolation step for some reason (e.g. + // because we have an indefinite duration which means we'll never get past the + // first value) and instead attempt to add that empty value to the underlying + // value. + // In any case, the expected result is that nothing is added. + if (srcTransforms.IsEmpty()) return NS_OK; + + // Stick the src on the end of the array + const SVGTransformSMILData& srcTransform = srcTransforms[0]; + SVGTransformSMILData* result = + dstTransforms.AppendElement(srcTransform, fallible); + NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY); + + return NS_OK; +} + +nsresult SVGTransformListSMILType::ComputeDistance(const SMILValue& aFrom, + const SMILValue& aTo, + double& aDistance) const { + MOZ_ASSERT(aFrom.mType == aTo.mType, + "Can't compute difference between different SMIL types"); + MOZ_ASSERT(aFrom.mType == this, "Unexpected SMIL type"); + + const TransformArray* fromTransforms = + static_cast(aFrom.mU.mPtr); + const TransformArray* toTransforms = + static_cast(aTo.mU.mPtr); + + // ComputeDistance is only used for calculating distances between single + // values in a values array which necessarily have the same type + // + // So we should only have one transform in each array and they should be of + // the same type + NS_ASSERTION(fromTransforms->Length() == 1, + "Wrong number of elements in from value"); + NS_ASSERTION(toTransforms->Length() == 1, + "Wrong number of elements in to value"); + + const SVGTransformSMILData& fromTransform = (*fromTransforms)[0]; + const SVGTransformSMILData& toTransform = (*toTransforms)[0]; + NS_ASSERTION(fromTransform.mTransformType == toTransform.mTransformType, + "Incompatible transform types to calculate distance between"); + + switch (fromTransform.mTransformType) { + // We adopt the SVGT1.2 notions of distance here + // See: http://www.w3.org/TR/SVGTiny12/animate.html#complexDistances + // (As discussed in bug #469040) + case SVG_TRANSFORM_TRANSLATE: + case SVG_TRANSFORM_SCALE: { + const float& a_tx = fromTransform.mParams[0]; + const float& a_ty = fromTransform.mParams[1]; + const float& b_tx = toTransform.mParams[0]; + const float& b_ty = toTransform.mParams[1]; + aDistance = sqrt(pow(a_tx - b_tx, 2) + (pow(a_ty - b_ty, 2))); + } break; + + case SVG_TRANSFORM_ROTATE: + case SVG_TRANSFORM_SKEWX: + case SVG_TRANSFORM_SKEWY: { + const float& a = fromTransform.mParams[0]; + const float& b = toTransform.mParams[0]; + aDistance = std::fabs(a - b); + } break; + + default: + NS_ERROR("Got bad transform types for calculating distances"); + aDistance = 1.0; + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult SVGTransformListSMILType::Interpolate(const SMILValue& aStartVal, + const SMILValue& aEndVal, + double aUnitDistance, + SMILValue& aResult) const { + MOZ_ASSERT(aStartVal.mType == aEndVal.mType, + "Can't interpolate between different SMIL types"); + MOZ_ASSERT(aStartVal.mType == this, "Unexpected type for interpolation"); + MOZ_ASSERT(aResult.mType == this, "Unexpected result type"); + + const TransformArray& startTransforms = + (*static_cast(aStartVal.mU.mPtr)); + const TransformArray& endTransforms( + *static_cast(aEndVal.mU.mPtr)); + + // We may have 0..n transforms in the start transform array (the base + // value) but we should only have 1 transform in the end transform array + NS_ASSERTION(endTransforms.Length() == 1, + "Invalid end-point for interpolating between transform values"); + + // The end point should never be a matrix transform + const SVGTransformSMILData& endTransform = endTransforms[0]; + NS_ASSERTION(endTransform.mTransformType != SVG_TRANSFORM_MATRIX, + "End point for interpolation should not be a matrix transform"); + + // If we have 0 or more than 1 transform in the start transform array then we + // just interpolate from 0, 0, 0 + // Likewise, even if there's only 1 transform in the start transform array + // then if the type of the start transform doesn't match the end then we + // can't interpolate and should just use 0, 0, 0 + static float identityParams[3] = {0.f}; + const float* startParams = nullptr; + if (startTransforms.Length() == 1) { + const SVGTransformSMILData& startTransform = startTransforms[0]; + if (startTransform.mTransformType == endTransform.mTransformType) { + startParams = startTransform.mParams; + } + } + if (!startParams) { + startParams = identityParams; + } + + const float* endParams = endTransform.mParams; + + // Interpolate between the params + float newParams[3]; + for (int i = 0; i <= 2; ++i) { + const float& a = startParams[i]; + const float& b = endParams[i]; + newParams[i] = static_cast(a + (b - a) * aUnitDistance); + } + + // Make the result + SVGTransformSMILData resultTransform(endTransform.mTransformType, newParams); + + // Clear the way for it in the result array + TransformArray& dstTransforms = + (*static_cast(aResult.mU.mPtr)); + dstTransforms.Clear(); + + // Assign the result + SVGTransformSMILData* transform = + dstTransforms.AppendElement(resultTransform, fallible); + NS_ENSURE_TRUE(transform, NS_ERROR_OUT_OF_MEMORY); + + return NS_OK; +} + +//---------------------------------------------------------------------- +// Transform array accessors + +// static +nsresult SVGTransformListSMILType::AppendTransform( + const SVGTransformSMILData& aTransform, SMILValue& aValue) { + MOZ_ASSERT(aValue.mType == Singleton(), "Unexpected SMIL value type"); + + TransformArray& transforms = *static_cast(aValue.mU.mPtr); + return transforms.AppendElement(aTransform, fallible) + ? NS_OK + : NS_ERROR_OUT_OF_MEMORY; +} + +// static +bool SVGTransformListSMILType::AppendTransforms(const SVGTransformList& aList, + SMILValue& aValue) { + MOZ_ASSERT(aValue.mType == Singleton(), "Unexpected SMIL value type"); + + TransformArray& transforms = *static_cast(aValue.mU.mPtr); + + if (!transforms.SetCapacity(transforms.Length() + aList.Length(), fallible)) + return false; + + for (uint32_t i = 0; i < aList.Length(); ++i) { + // No need to check the return value below since we have already allocated + // the necessary space + MOZ_ALWAYS_TRUE( + transforms.AppendElement(SVGTransformSMILData(aList[i]), fallible)); + } + return true; +} + +// static +bool SVGTransformListSMILType::GetTransforms( + const SMILValue& aValue, FallibleTArray& aTransforms) { + MOZ_ASSERT(aValue.mType == Singleton(), "Unexpected SMIL value type"); + + const TransformArray& smilTransforms = + *static_cast(aValue.mU.mPtr); + + aTransforms.Clear(); + if (!aTransforms.SetCapacity(smilTransforms.Length(), fallible)) return false; + + for (uint32_t i = 0; i < smilTransforms.Length(); ++i) { + // No need to check the return value below since we have already allocated + // the necessary space + (void)aTransforms.AppendElement(smilTransforms[i].ToSVGTransform(), + fallible); + } + return true; +} + +} // namespace mozilla diff --git a/dom/svg/SVGTransformListSMILType.h b/dom/svg/SVGTransformListSMILType.h new file mode 100644 index 0000000000..409acf5ad0 --- /dev/null +++ b/dom/svg/SVGTransformListSMILType.h @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTRANSFORMLISTSMILTYPE_H_ +#define DOM_SVG_SVGTRANSFORMLISTSMILTYPE_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/SMILType.h" +#include "nsTArray.h" + +namespace mozilla { + +class SMILValue; +class SVGTransform; +class SVGTransformList; +class SVGTransformSMILData; + +//////////////////////////////////////////////////////////////////////// +// SVGTransformListSMILType +// +// Operations for animating an SVGTransformList. +// +// This class is confused somewhat by the fact that: +// (i) An element animates an SVGTransformList +// (ii) BUT only allows the user to specify animation values +// for an SVGTransform +// +// This may be rectified in a future edition of SVG but for now it means that +// the underlying value of an animation may be something of the form: +// +// rotate(90) scale(2) skewX(50) +// +// BUT the animation values can only ever be SINGLE transform operations such as +// +// rotate(90) +// +// (actually the syntax here is: +// element and so these values can only ever contain 0 or +// 1 TRANSFORM elements as the syntax doesn't allow more. (A "value" here is +// a single element in the values array such as "0 50 20" above.) +// +// Likewise ComputeDistance() only ever operates within the values specified on +// an element so similar conditions hold. +// +// However, SandwichAdd() combines with a base value which may contain 0..n +// transforms either because the base value of the attribute specifies a series +// of transforms, e.g. +// +// +// +// +// +// or because several animations target the same attribute and are additive and +// so are simply appended on to the transformation array, e.g. +// +// +// +// +// +// +// +// Similar conditions hold for Interpolate() which in cases such as to-animation +// may have use a start-value the base value of the target attribute (which as +// we have seen above can contain 0..n elements) whilst the end-value comes from +// the and so can only hold 1 transform. +// +class SVGTransformListSMILType : public SMILType { + public: + // Singleton for SMILValue objects to hold onto. + static SVGTransformListSMILType* Singleton() { + static SVGTransformListSMILType sSingleton; + return &sSingleton; + } + + protected: + // SMILType Methods + // ------------------- + void Init(SMILValue& aValue) const override; + void Destroy(SMILValue& aValue) const override; + nsresult Assign(SMILValue& aDest, const SMILValue& aSrc) const override; + bool IsEqual(const SMILValue& aLeft, const SMILValue& aRight) const override; + nsresult Add(SMILValue& aDest, const SMILValue& aValueToAdd, + uint32_t aCount) const override; + nsresult SandwichAdd(SMILValue& aDest, + const SMILValue& aValueToAdd) const override; + nsresult ComputeDistance(const SMILValue& aFrom, const SMILValue& aTo, + double& aDistance) const override; + nsresult Interpolate(const SMILValue& aStartVal, const SMILValue& aEndVal, + double aUnitDistance, SMILValue& aResult) const override; + + public: + // Transform array accessors + // ------------------------- + static nsresult AppendTransform(const SVGTransformSMILData& aTransform, + SMILValue& aValue); + static bool AppendTransforms(const SVGTransformList& aList, + SMILValue& aValue); + static bool GetTransforms(const SMILValue& aValue, + FallibleTArray& aTransforms); + + private: + // Private constructor: prevent instances beyond my singleton. + constexpr SVGTransformListSMILType() = default; +}; + +} // end namespace mozilla + +#endif // DOM_SVG_SVGTRANSFORMLISTSMILTYPE_H_ diff --git a/dom/svg/SVGTransformableElement.cpp b/dom/svg/SVGTransformableElement.cpp new file mode 100644 index 0000000000..1704eb8c74 --- /dev/null +++ b/dom/svg/SVGTransformableElement.cpp @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGTransformableElement.h" + +#include "DOMSVGAnimatedTransformList.h" +#include "mozilla/dom/MutationEventBinding.h" +#include "nsContentUtils.h" +#include "nsIFrame.h" + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +already_AddRefed +SVGTransformableElement::Transform() { + // We're creating a DOM wrapper, so we must tell GetAnimatedTransformList + // to allocate the DOMSVGAnimatedTransformList if it hasn't already done so: + return DOMSVGAnimatedTransformList::GetDOMWrapper( + GetAnimatedTransformList(DO_ALLOCATE), this); +} + +//---------------------------------------------------------------------- +// nsIContent methods + +nsChangeHint SVGTransformableElement::GetAttributeChangeHint( + const nsAtom* aAttribute, int32_t aModType) const { + nsChangeHint retval = + SVGElement::GetAttributeChangeHint(aAttribute, aModType); + if (aAttribute == nsGkAtoms::transform || + aAttribute == nsGkAtoms::mozAnimateMotionDummyAttr) { + nsIFrame* frame = + const_cast(this)->GetPrimaryFrame(); + retval |= nsChangeHint_InvalidateRenderingObservers; + if (!frame || frame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { + return retval; + } + + bool isAdditionOrRemoval = false; + if (aModType == MutationEvent_Binding::ADDITION || + aModType == MutationEvent_Binding::REMOVAL) { + isAdditionOrRemoval = true; + } else { + MOZ_ASSERT(aModType == MutationEvent_Binding::MODIFICATION, + "Unknown modification type."); + if (!mTransforms || !mTransforms->HasTransform()) { + // New value is empty, treat as removal. + // FIXME: Should we just rely on CreatedOrRemovedOnLastChange? + isAdditionOrRemoval = true; + } else if (mTransforms->CreatedOrRemovedOnLastChange()) { + // Old value was empty, treat as addition. + isAdditionOrRemoval = true; + } + } + + if (isAdditionOrRemoval) { + retval |= nsChangeHint_ComprehensiveAddOrRemoveTransform; + } else { + // We just assume the old and new transforms are different. + retval |= nsChangeHint_UpdatePostTransformOverflow | + nsChangeHint_UpdateTransformLayer; + } + } + return retval; +} + +bool SVGTransformableElement::IsEventAttributeNameInternal(nsAtom* aName) { + return nsContentUtils::IsEventAttributeName(aName, EventNameType_SVGGraphic); +} + +//---------------------------------------------------------------------- +// SVGElement overrides + +gfxMatrix SVGTransformableElement::PrependLocalTransformsTo( + const gfxMatrix& aMatrix, SVGTransformTypes aWhich) const { + if (aWhich == eChildToUserSpace) { + // We don't have any eUserSpaceToParent transforms. (Sub-classes that do + // must override this function and handle that themselves.) + return aMatrix; + } + return GetUserToParentTransform(mAnimateMotionTransform.get(), + mTransforms.get()) * + aMatrix; +} + +const gfx::Matrix* SVGTransformableElement::GetAnimateMotionTransform() const { + return mAnimateMotionTransform.get(); +} + +void SVGTransformableElement::SetAnimateMotionTransform( + const gfx::Matrix* aMatrix) { + if ((!aMatrix && !mAnimateMotionTransform) || + (aMatrix && mAnimateMotionTransform && + aMatrix->FuzzyEquals(*mAnimateMotionTransform))) { + return; + } + bool transformSet = mTransforms && mTransforms->IsExplicitlySet(); + bool prevSet = mAnimateMotionTransform || transformSet; + mAnimateMotionTransform = + aMatrix ? MakeUnique(*aMatrix) : nullptr; + bool nowSet = mAnimateMotionTransform || transformSet; + int32_t modType; + if (prevSet && !nowSet) { + modType = MutationEvent_Binding::REMOVAL; + } else if (!prevSet && nowSet) { + modType = MutationEvent_Binding::ADDITION; + } else { + modType = MutationEvent_Binding::MODIFICATION; + } + DidAnimateTransformList(modType); + nsIFrame* frame = GetPrimaryFrame(); + if (frame) { + // If the result of this transform and any other transforms on this frame + // is the identity matrix, then DoApplyRenderingChangeToTree won't handle + // our nsChangeHint_UpdateTransformLayer hint since aFrame->IsTransformed() + // will return false. That's fine, but we still need to schedule a repaint, + // and that won't otherwise happen. Since it's cheap to call SchedulePaint, + // we don't bother to check IsTransformed(). + frame->SchedulePaint(); + } +} + +SVGAnimatedTransformList* SVGTransformableElement::GetAnimatedTransformList( + uint32_t aFlags) { + if (!mTransforms && (aFlags & DO_ALLOCATE)) { + mTransforms = MakeUnique(); + } + return mTransforms.get(); +} + +/* static */ +gfxMatrix SVGTransformableElement::GetUserToParentTransform( + const gfx::Matrix* aAnimateMotionTransform, + const SVGAnimatedTransformList* aTransforms) { + gfxMatrix result; + + if (aAnimateMotionTransform) { + result.PreMultiply(ThebesMatrix(*aAnimateMotionTransform)); + } + + if (aTransforms) { + result.PreMultiply(aTransforms->GetAnimValue().GetConsolidationMatrix()); + } + + return result; +} + +} // namespace mozilla::dom diff --git a/dom/svg/SVGTransformableElement.h b/dom/svg/SVGTransformableElement.h new file mode 100644 index 0000000000..2f6a504ce2 --- /dev/null +++ b/dom/svg/SVGTransformableElement.h @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_SVG_SVGTRANSFORMABLEELEMENT_H_ +#define DOM_SVG_SVGTRANSFORMABLEELEMENT_H_ + +#include "gfxMatrix.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/SVGAnimatedTransformList.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla::dom { + +class DOMSVGAnimatedTransformList; +class SVGGraphicsElement; +class SVGMatrix; +class SVGRect; +struct SVGBoundingBoxOptions; + +class SVGTransformableElement : public SVGElement { + public: + explicit SVGTransformableElement(already_AddRefed&& aNodeInfo) + : SVGElement(std::move(aNodeInfo)) {} + virtual ~SVGTransformableElement() = default; + + nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override = 0; + + // WebIDL + already_AddRefed Transform(); + + // nsIContent interface + nsChangeHint GetAttributeChangeHint(const nsAtom* aAttribute, + int32_t aModType) const override; + + // SVGElement overrides + bool IsEventAttributeNameInternal(nsAtom* aName) override; + + gfxMatrix PrependLocalTransformsTo( + const gfxMatrix& aMatrix, + SVGTransformTypes aWhich = eAllTransforms) const override; + const gfx::Matrix* GetAnimateMotionTransform() const override; + void SetAnimateMotionTransform(const gfx::Matrix* aMatrix) override; + + SVGAnimatedTransformList* GetAnimatedTransformList( + uint32_t aFlags = 0) override; + nsStaticAtom* GetTransformListAttrName() const override { + return nsGkAtoms::transform; + } + + bool IsTransformable() override { return true; } + + protected: + /** + * Helper for overrides of PrependLocalTransformsTo. If both arguments are + * provided they are multiplied in the order in which the arguments appear, + * and the result is returned. If neither argument is provided, the identity + * matrix is returned. If only one argument is provided its transform is + * returned. + */ + static gfxMatrix GetUserToParentTransform( + const gfx::Matrix* aAnimateMotionTransform, + const SVGAnimatedTransformList* aTransforms); + + UniquePtr mTransforms; + + // XXX maybe move this to property table, to save space on un-animated elems? + UniquePtr mAnimateMotionTransform; +}; + +} // namespace mozilla::dom + +#endif // DOM_SVG_SVGTRANSFORMABLEELEMENT_H_ diff --git a/dom/svg/SVGUseElement.cpp b/dom/svg/SVGUseElement.cpp new file mode 100644 index 0000000000..3a2bfc63e1 --- /dev/null +++ b/dom/svg/SVGUseElement.cpp @@ -0,0 +1,669 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/SVGUseElement.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticPrefs_svg.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/SVGUseFrame.h" +#include "mozilla/URLExtraData.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/ReferrerInfo.h" +#include "mozilla/dom/ShadowIncludingTreeIterator.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/dom/SVGGraphicsElement.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "mozilla/dom/SVGUseElementBinding.h" +#include "nsGkAtoms.h" +#include "nsContentUtils.h" +#include "nsIReferrerInfo.h" +#include "nsIURI.h" +#include "SVGGeometryProperty.h" + +NS_IMPL_NS_NEW_SVG_ELEMENT(Use) + +namespace mozilla::dom { + +JSObject* SVGUseElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return SVGUseElement_Binding::Wrap(aCx, this, aGivenProto); +} + +//////////////////////////////////////////////////////////////////////// +// implementation + +SVGElement::LengthInfo SVGUseElement::sLengthInfo[4] = { + {nsGkAtoms::x, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::y, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, + {nsGkAtoms::width, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::X}, + {nsGkAtoms::height, 0, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER, + SVGContentUtils::Y}, +}; + +SVGElement::StringInfo SVGUseElement::sStringInfo[2] = { + {nsGkAtoms::href, kNameSpaceID_None, true}, + {nsGkAtoms::href, kNameSpaceID_XLink, true}}; + +//---------------------------------------------------------------------- +// nsISupports methods + +NS_IMPL_CYCLE_COLLECTION_CLASS(SVGUseElement) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SVGUseElement, + SVGUseElementBase) + nsAutoScriptBlocker scriptBlocker; + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginal) + tmp->UnlinkSource(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SVGUseElement, + SVGUseElementBase) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginal) + tmp->mReferencedElementTracker.Traverse(&cb); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(SVGUseElement, SVGUseElementBase, + nsIMutationObserver) + +//---------------------------------------------------------------------- +// Implementation + +SVGUseElement::SVGUseElement( + already_AddRefed&& aNodeInfo) + : SVGUseElementBase(std::move(aNodeInfo)), + mReferencedElementTracker(this) {} + +SVGUseElement::~SVGUseElement() { + UnlinkSource(); + MOZ_DIAGNOSTIC_ASSERT(!OwnerDoc()->SVGUseElementNeedsShadowTreeUpdate(*this), + "Dying without unbinding?"); +} + +namespace SVGT = SVGGeometryProperty::Tags; + +//---------------------------------------------------------------------- +// nsINode methods + +void SVGUseElement::ProcessAttributeChange(int32_t aNamespaceID, + nsAtom* aAttribute) { + if (aNamespaceID == kNameSpaceID_None) { + if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height) { + const bool hadValidDimensions = HasValidDimensions(); + const bool isUsed = OurWidthAndHeightAreUsed(); + if (isUsed) { + SyncWidthOrHeight(aAttribute); + } + + if (auto* frame = GetFrame()) { + frame->DimensionAttributeChanged(hadValidDimensions, isUsed); + } + } + } + + if ((aNamespaceID == kNameSpaceID_XLink || + aNamespaceID == kNameSpaceID_None) && + aAttribute == nsGkAtoms::href) { + // We're changing our nature, clear out the clone information. + if (auto* frame = GetFrame()) { + frame->HrefChanged(); + } + mOriginal = nullptr; + UnlinkSource(); + TriggerReclone(); + } +} + +void SVGUseElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, + bool aNotify) { + ProcessAttributeChange(aNamespaceID, aAttribute); + return SVGUseElementBase::AfterSetAttr(aNamespaceID, aAttribute, aValue, + aOldValue, aSubjectPrincipal, aNotify); +} + +nsresult SVGUseElement::Clone(dom::NodeInfo* aNodeInfo, + nsINode** aResult) const { + *aResult = nullptr; + SVGUseElement* it = + new (aNodeInfo->NodeInfoManager()) SVGUseElement(do_AddRef(aNodeInfo)); + + nsCOMPtr kungFuDeathGrip(it); + nsresult rv1 = it->Init(); + nsresult rv2 = const_cast(this)->CopyInnerTo(it); + + // SVGUseElement specific portion - record who we cloned from + it->mOriginal = const_cast(this); + + if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) { + kungFuDeathGrip.swap(*aResult); + } + + return NS_FAILED(rv1) ? rv1 : rv2; +} + +nsresult SVGUseElement::BindToTree(BindContext& aContext, nsINode& aParent) { + nsresult rv = SVGUseElementBase::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + TriggerReclone(); + return NS_OK; +} + +void SVGUseElement::UnbindFromTree(bool aNullParent) { + SVGUseElementBase::UnbindFromTree(aNullParent); + OwnerDoc()->UnscheduleSVGUseElementShadowTreeUpdate(*this); +} + +already_AddRefed SVGUseElement::Href() { + return mStringAttributes[HREF].IsExplicitlySet() + ? mStringAttributes[HREF].ToDOMAnimatedString(this) + : mStringAttributes[XLINK_HREF].ToDOMAnimatedString(this); +} + +//---------------------------------------------------------------------- + +already_AddRefed SVGUseElement::X() { + return mLengthAttributes[ATTR_X].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGUseElement::Y() { + return mLengthAttributes[ATTR_Y].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGUseElement::Width() { + return mLengthAttributes[ATTR_WIDTH].ToDOMAnimatedLength(this); +} + +already_AddRefed SVGUseElement::Height() { + return mLengthAttributes[ATTR_HEIGHT].ToDOMAnimatedLength(this); +} + +//---------------------------------------------------------------------- +// nsIMutationObserver methods + +void SVGUseElement::CharacterDataChanged(nsIContent* aContent, + const CharacterDataChangeInfo&) { + if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker.get(), + aContent)) { + TriggerReclone(); + } +} + +void SVGUseElement::AttributeChanged(Element* aElement, int32_t aNamespaceID, + nsAtom* aAttribute, int32_t aModType, + const nsAttrValue* aOldValue) { + if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker.get(), + aElement)) { + TriggerReclone(); + } +} + +void SVGUseElement::ContentAppended(nsIContent* aFirstNewContent) { + // FIXME(emilio, bug 1442336): Why does this check the parent but + // ContentInserted the child? + if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker.get(), + aFirstNewContent->GetParent())) { + TriggerReclone(); + } +} + +void SVGUseElement::ContentInserted(nsIContent* aChild) { + // FIXME(emilio, bug 1442336): Why does this check the child but + // ContentAppended the parent? + if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker.get(), + aChild)) { + TriggerReclone(); + } +} + +void SVGUseElement::ContentRemoved(nsIContent* aChild, + nsIContent* aPreviousSibling) { + if (nsContentUtils::IsInSameAnonymousTree(mReferencedElementTracker.get(), + aChild)) { + TriggerReclone(); + } +} + +void SVGUseElement::NodeWillBeDestroyed(nsINode* aNode) { + nsCOMPtr kungFuDeathGrip(this); + UnlinkSource(); +} + +// Returns whether this node could ever be displayed. +static bool NodeCouldBeRendered(const nsINode& aNode) { + if (aNode.IsSVGElement(nsGkAtoms::symbol)) { + // Only elements in the root of a shadow tree are + // displayed. + auto* shadowRoot = ShadowRoot::FromNodeOrNull(aNode.GetParentNode()); + return shadowRoot && shadowRoot->Host()->IsSVGElement(nsGkAtoms::use); + } + // TODO: Do we have other cases we can optimize out easily? + return true; +} + +// can be used (no pun intended) to trivially cause an explosion of +// clones that could potentially DoS the browser. We have a configurable limit +// to control this. +static bool IsTooMuchRecursion(uint32_t aCount) { + switch (StaticPrefs::svg_use_element_recursive_clone_limit_enabled()) { + case 0: + return false; + case 1: + break; + default: + if (!XRE_IsParentProcess()) { + return false; + } + break; + } + return aCount >= StaticPrefs::svg_use_element_recursive_clone_limit(); +} + +// Circular loop detection, plus detection of whether this shadow tree is +// rendered at all. +auto SVGUseElement::ScanAncestors(const Element& aTarget) const -> ScanResult { + uint32_t count = 0; + return ScanAncestorsInternal(aTarget, count); +} + +auto SVGUseElement::ScanAncestorsInternal(const Element& aTarget, + uint32_t& aCount) const + -> ScanResult { + if (&aTarget == this) { + return ScanResult::CyclicReference; + } + if (mOriginal) { + if (IsTooMuchRecursion(++aCount)) { + return ScanResult::TooDeep; + } + auto result = mOriginal->ScanAncestorsInternal(aTarget, aCount); + switch (result) { + case ScanResult::TooDeep: + case ScanResult::CyclicReference: + return result; + case ScanResult::Ok: + case ScanResult::Invisible: + break; + } + } + + auto result = ScanResult::Ok; + for (nsINode* parent = GetParentOrShadowHostNode(); parent; + parent = parent->GetParentOrShadowHostNode()) { + if (parent == &aTarget) { + return ScanResult::CyclicReference; + } + if (auto* use = SVGUseElement::FromNode(*parent)) { + if (IsTooMuchRecursion(++aCount)) { + return ScanResult::TooDeep; + } + if (mOriginal && use->mOriginal == mOriginal) { + return ScanResult::CyclicReference; + } + } + // Do we have other similar cases we can optimize out easily? + if (!NodeCouldBeRendered(*parent)) { + // NOTE(emilio): We can't just return here. If we're cyclic, we need to + // know. + result = ScanResult::Invisible; + } + } + return result; +} + +//---------------------------------------------------------------------- + +static bool IsForbiddenUseNode(const nsINode& aNode) { + if (!aNode.IsElement()) { + return false; + } + const auto* svg = SVGElement::FromNode(aNode); + return !svg || !svg->IsSVGGraphicsElement(); +} + +static void CollectForbiddenNodes(Element& aRoot, + nsTArray>& aNodes) { + auto iter = dom::ShadowIncludingTreeIterator(aRoot); + while (iter) { + nsINode* node = *iter; + if (IsForbiddenUseNode(*node)) { + aNodes.AppendElement(node); + iter.SkipChildren(); + continue; + } + ++iter; + } +} + +// SVG1 restricted trees to SVGGraphicsElements. +// https://www.w3.org/TR/SVG11/struct.html#UseElement: +// +// Any ‘svg’, ‘symbol’, ‘g’, graphics element or other ‘use’ is potentially a +// template object that can be re-used (i.e., "instanced") in the SVG +// document via a ‘use’ element. The ‘use’ element references another element +// and indicates that the graphical contents of that element is +// included/drawn at that given point in the document. +// +// SVG2 doesn't have that same restriction. +// https://www.w3.org/TR/SVG2/struct.html#UseShadowTree: +// +// Previous versions of SVG restricted the contents of the shadow tree to SVG +// graphics elements. This specification allows any valid SVG document +// subtree to be cloned. Cloning non-graphical content, however, will not +// usually have any visible effect. +// +// But it's pretty ambiguous as to what the behavior should be for some +// elements, because + + +Loading the below iframe should not crash Firefox in Stylo mode. + + + \ No newline at end of file diff --git a/dom/svg/crashtests/1329093-2.html b/dom/svg/crashtests/1329093-2.html new file mode 100644 index 0000000000..444b7aae8e --- /dev/null +++ b/dom/svg/crashtests/1329093-2.html @@ -0,0 +1,28 @@ + + + + + + +Loading the below iframe should not crash Firefox in Stylo mode. + + + + + + + + + diff --git a/dom/svg/crashtests/1329849-1.svg b/dom/svg/crashtests/1329849-1.svg new file mode 100644 index 0000000000..350e549efd --- /dev/null +++ b/dom/svg/crashtests/1329849-1.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/dom/svg/crashtests/1329849-2.svg b/dom/svg/crashtests/1329849-2.svg new file mode 100644 index 0000000000..ec340e2316 --- /dev/null +++ b/dom/svg/crashtests/1329849-2.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/dom/svg/crashtests/1329849-3.svg b/dom/svg/crashtests/1329849-3.svg new file mode 100644 index 0000000000..a263e083fe --- /dev/null +++ b/dom/svg/crashtests/1329849-3.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/dom/svg/crashtests/1329849-4.svg b/dom/svg/crashtests/1329849-4.svg new file mode 100644 index 0000000000..b8bc061c5f --- /dev/null +++ b/dom/svg/crashtests/1329849-4.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/dom/svg/crashtests/1329849-5.svg b/dom/svg/crashtests/1329849-5.svg new file mode 100644 index 0000000000..16390ccb04 --- /dev/null +++ b/dom/svg/crashtests/1329849-5.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/dom/svg/crashtests/1329849-6.svg b/dom/svg/crashtests/1329849-6.svg new file mode 100644 index 0000000000..0a928393c7 --- /dev/null +++ b/dom/svg/crashtests/1329849-6.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/dom/svg/crashtests/1343147.svg b/dom/svg/crashtests/1343147.svg new file mode 100644 index 0000000000..d9c2611ca8 --- /dev/null +++ b/dom/svg/crashtests/1343147.svg @@ -0,0 +1,13 @@ + + + hello + diff --git a/dom/svg/crashtests/1347617-1.svg b/dom/svg/crashtests/1347617-1.svg new file mode 100644 index 0000000000..b65c63fbf8 --- /dev/null +++ b/dom/svg/crashtests/1347617-1.svg @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/dom/svg/crashtests/1347617-2.svg b/dom/svg/crashtests/1347617-2.svg new file mode 100644 index 0000000000..8355ff6ade --- /dev/null +++ b/dom/svg/crashtests/1347617-2.svg @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/dom/svg/crashtests/1347617-3.svg b/dom/svg/crashtests/1347617-3.svg new file mode 100644 index 0000000000..2dd32cde32 --- /dev/null +++ b/dom/svg/crashtests/1347617-3.svg @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/dom/svg/crashtests/1402798.html b/dom/svg/crashtests/1402798.html new file mode 100644 index 0000000000..43d7a77e86 --- /dev/null +++ b/dom/svg/crashtests/1402798.html @@ -0,0 +1,11 @@ + + + + +aa diff --git a/dom/svg/crashtests/1419250-1.html b/dom/svg/crashtests/1419250-1.html new file mode 100644 index 0000000000..fb0d0ea22c --- /dev/null +++ b/dom/svg/crashtests/1419250-1.html @@ -0,0 +1 @@ + diff --git a/dom/svg/crashtests/1420492.html b/dom/svg/crashtests/1420492.html new file mode 100644 index 0000000000..3c5b22ff3e --- /dev/null +++ b/dom/svg/crashtests/1420492.html @@ -0,0 +1,3 @@ + + + diff --git a/dom/svg/crashtests/1477853.html b/dom/svg/crashtests/1477853.html new file mode 100644 index 0000000000..f2a1d37657 --- /dev/null +++ b/dom/svg/crashtests/1477853.html @@ -0,0 +1,10 @@ + + + + + + diff --git a/dom/svg/crashtests/1486488.html b/dom/svg/crashtests/1486488.html new file mode 100644 index 0000000000..ba61cb277b --- /dev/null +++ b/dom/svg/crashtests/1486488.html @@ -0,0 +1,11 @@ + + + + + + + diff --git a/dom/svg/crashtests/1493447.html b/dom/svg/crashtests/1493447.html new file mode 100644 index 0000000000..648f6149b9 --- /dev/null +++ b/dom/svg/crashtests/1493447.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/dom/svg/crashtests/1507961-1.html b/dom/svg/crashtests/1507961-1.html new file mode 100644 index 0000000000..a94d5872fb --- /dev/null +++ b/dom/svg/crashtests/1507961-1.html @@ -0,0 +1,4066 @@ + + + + + + + + + +
+[a + +
+ +^iVw;'u1fo^L7$C +NG+]s9<^/I[=LD\ft/ + + + + +"#XR_tF_[/W~~E + + + + + + + + + + + + + + + +f#1s=6$E"d;f +
    +
  • + +
      +
    1. +P9X\~%wy10)yC)AbW"H + +GVS0d@AbMN + +
    2. +
    +
  • +
  • +
    DHk!;hgQqQ|j6zcXVL_G
    + +
  • +
+