summaryrefslogtreecommitdiffstats
path: root/layout/svg
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--layout/svg/AutoReferenceChainGuard.h169
-rw-r--r--layout/svg/CSSClipPathInstance.cpp243
-rw-r--r--layout/svg/CSSClipPathInstance.h69
-rw-r--r--layout/svg/CSSFilterInstance.cpp356
-rw-r--r--layout/svg/CSSFilterInstance.h146
-rw-r--r--layout/svg/FilterInstance.cpp903
-rw-r--r--layout/svg/FilterInstance.h405
-rw-r--r--layout/svg/ISVGDisplayableFrame.h157
-rw-r--r--layout/svg/ISVGSVGFrame.h30
-rw-r--r--layout/svg/SVGAFrame.cpp102
-rw-r--r--layout/svg/SVGClipPathFrame.cpp489
-rw-r--r--layout/svg/SVGClipPathFrame.h170
-rw-r--r--layout/svg/SVGContainerFrame.cpp427
-rw-r--r--layout/svg/SVGContainerFrame.h163
-rw-r--r--layout/svg/SVGContextPaint.cpp370
-rw-r--r--layout/svg/SVGContextPaint.h287
-rw-r--r--layout/svg/SVGFEContainerFrame.cpp108
-rw-r--r--layout/svg/SVGFEImageFrame.cpp172
-rw-r--r--layout/svg/SVGFELeafFrame.cpp105
-rw-r--r--layout/svg/SVGFEUnstyledLeafFrame.cpp88
-rw-r--r--layout/svg/SVGFilterFrame.cpp177
-rw-r--r--layout/svg/SVGFilterFrame.h93
-rw-r--r--layout/svg/SVGFilterInstance.cpp447
-rw-r--r--layout/svg/SVGFilterInstance.h267
-rw-r--r--layout/svg/SVGForeignObjectFrame.cpp563
-rw-r--r--layout/svg/SVGForeignObjectFrame.h108
-rw-r--r--layout/svg/SVGGFrame.cpp58
-rw-r--r--layout/svg/SVGGFrame.h55
-rw-r--r--layout/svg/SVGGeometryFrame.cpp898
-rw-r--r--layout/svg/SVGGeometryFrame.h208
-rw-r--r--layout/svg/SVGGradientFrame.cpp606
-rw-r--r--layout/svg/SVGGradientFrame.h203
-rw-r--r--layout/svg/SVGImageContext.cpp88
-rw-r--r--layout/svg/SVGImageContext.h136
-rw-r--r--layout/svg/SVGImageFrame.cpp880
-rw-r--r--layout/svg/SVGImageFrame.h115
-rw-r--r--layout/svg/SVGInnerSVGFrame.cpp40
-rw-r--r--layout/svg/SVGInnerSVGFrame.h47
-rw-r--r--layout/svg/SVGIntegrationUtils.cpp1256
-rw-r--r--layout/svg/SVGIntegrationUtils.h269
-rw-r--r--layout/svg/SVGMarkerFrame.cpp240
-rw-r--r--layout/svg/SVGMarkerFrame.h164
-rw-r--r--layout/svg/SVGMaskFrame.cpp190
-rw-r--r--layout/svg/SVGMaskFrame.h111
-rw-r--r--layout/svg/SVGObserverUtils.cpp1766
-rw-r--r--layout/svg/SVGObserverUtils.h420
-rw-r--r--layout/svg/SVGOuterSVGFrame.cpp1033
-rw-r--r--layout/svg/SVGOuterSVGFrame.h277
-rw-r--r--layout/svg/SVGPaintServerFrame.cpp16
-rw-r--r--layout/svg/SVGPaintServerFrame.h82
-rw-r--r--layout/svg/SVGPatternFrame.cpp714
-rw-r--r--layout/svg/SVGPatternFrame.h136
-rw-r--r--layout/svg/SVGStopFrame.cpp105
-rw-r--r--layout/svg/SVGSwitchFrame.cpp289
-rw-r--r--layout/svg/SVGSymbolFrame.cpp39
-rw-r--r--layout/svg/SVGSymbolFrame.h47
-rw-r--r--layout/svg/SVGTextFrame.cpp5413
-rw-r--r--layout/svg/SVGTextFrame.h589
-rw-r--r--layout/svg/SVGUseFrame.cpp161
-rw-r--r--layout/svg/SVGUseFrame.h68
-rw-r--r--layout/svg/SVGUtils.cpp1706
-rw-r--r--layout/svg/SVGUtils.h609
-rw-r--r--layout/svg/SVGViewFrame.cpp119
-rw-r--r--layout/svg/SVGViewportFrame.cpp273
-rw-r--r--layout/svg/SVGViewportFrame.h52
-rw-r--r--layout/svg/crashtests/1016145.svg5
-rw-r--r--layout/svg/crashtests/1028512.svg15
-rw-r--r--layout/svg/crashtests/1072758.html35
-rw-r--r--layout/svg/crashtests/1140080-1.svg11
-rw-r--r--layout/svg/crashtests/1149542-1.svg9
-rw-r--r--layout/svg/crashtests/1156581-1.svg12
-rw-r--r--layout/svg/crashtests/1182496-1.html21
-rw-r--r--layout/svg/crashtests/1209525-1.svg7
-rw-r--r--layout/svg/crashtests/1223281-1.svg24
-rw-r--r--layout/svg/crashtests/1234726-1.svg13
-rw-r--r--layout/svg/crashtests/1322537-1.html2
-rw-r--r--layout/svg/crashtests/1322537-2.html15
-rw-r--r--layout/svg/crashtests/1322852.html2
-rw-r--r--layout/svg/crashtests/1348564.svg7
-rw-r--r--layout/svg/crashtests/1402109.html11
-rw-r--r--layout/svg/crashtests/1402124.html10
-rw-r--r--layout/svg/crashtests/1402486.html12
-rw-r--r--layout/svg/crashtests/1403656-1.html19
-rw-r--r--layout/svg/crashtests/1403656-2.html21
-rw-r--r--layout/svg/crashtests/1403656-3.html8
-rw-r--r--layout/svg/crashtests/1403656-4.html9
-rw-r--r--layout/svg/crashtests/1403656-5.html11
-rw-r--r--layout/svg/crashtests/1404086.html2
-rw-r--r--layout/svg/crashtests/1421807-1.html5
-rw-r--r--layout/svg/crashtests/1421807-2.html15
-rw-r--r--layout/svg/crashtests/1422226.html39
-rw-r--r--layout/svg/crashtests/1425434-1.html59
-rw-r--r--layout/svg/crashtests/1443092-helper.svg6
-rw-r--r--layout/svg/crashtests/1443092.html34
-rw-r--r--layout/svg/crashtests/1454201-1.html52
-rw-r--r--layout/svg/crashtests/1467552-1.html15
-rw-r--r--layout/svg/crashtests/1474982.html2
-rw-r--r--layout/svg/crashtests/1480224.html6
-rw-r--r--layout/svg/crashtests/1480275.html15
-rw-r--r--layout/svg/crashtests/1502936.html11
-rw-r--r--layout/svg/crashtests/1504072.html4
-rw-r--r--layout/svg/crashtests/1504918.svg4
-rw-r--r--layout/svg/crashtests/1535517-1.svg11
-rw-r--r--layout/svg/crashtests/1536892.html13
-rw-r--r--layout/svg/crashtests/1539318-1.svg10
-rw-r--r--layout/svg/crashtests/1548985-1.html16
-rw-r--r--layout/svg/crashtests/1548985-2.svg4
-rw-r--r--layout/svg/crashtests/1555851.html8
-rw-r--r--layout/svg/crashtests/1563779.html19
-rw-r--r--layout/svg/crashtests/1600855.html8
-rw-r--r--layout/svg/crashtests/1601824.html7
-rw-r--r--layout/svg/crashtests/1605223-1.html4
-rw-r--r--layout/svg/crashtests/1609663.html34
-rw-r--r--layout/svg/crashtests/1671950.html27
-rw-r--r--layout/svg/crashtests/1678947.html21
-rw-r--r--layout/svg/crashtests/1693032.html21
-rw-r--r--layout/svg/crashtests/1696505.html9
-rw-r--r--layout/svg/crashtests/1755770-1.html18
-rw-r--r--layout/svg/crashtests/1755770-2.html19
-rw-r--r--layout/svg/crashtests/1758029-1.html40
-rw-r--r--layout/svg/crashtests/1764936-1.html16
-rw-r--r--layout/svg/crashtests/1804958.html4
-rw-r--r--layout/svg/crashtests/1810260.html28
-rw-r--r--layout/svg/crashtests/220165-1.svg21
-rw-r--r--layout/svg/crashtests/267650-1.svg4
-rw-r--r--layout/svg/crashtests/294022-1.svg17
-rw-r--r--layout/svg/crashtests/307314-1.svg9
-rw-r--r--layout/svg/crashtests/308615-1.svg10
-rw-r--r--layout/svg/crashtests/308917-1.svg35
-rw-r--r--layout/svg/crashtests/310436-1.svg28
-rw-r--r--layout/svg/crashtests/310638.svg35
-rw-r--r--layout/svg/crashtests/313737-1.xml16
-rw-r--r--layout/svg/crashtests/314244-1.xhtml26
-rw-r--r--layout/svg/crashtests/322185-1.svg6
-rw-r--r--layout/svg/crashtests/322215-1.svg31
-rw-r--r--layout/svg/crashtests/323704-1.svg12
-rw-r--r--layout/svg/crashtests/325427-1.svg20
-rw-r--r--layout/svg/crashtests/326495-1.svg16
-rw-r--r--layout/svg/crashtests/326974-1.svg21
-rw-r--r--layout/svg/crashtests/327706-1.svg9
-rw-r--r--layout/svg/crashtests/327711-1.svg19
-rw-r--r--layout/svg/crashtests/328137-1.svg24
-rw-r--r--layout/svg/crashtests/329848-1.svg1
-rw-r--r--layout/svg/crashtests/337408-1.xhtml21
-rw-r--r--layout/svg/crashtests/338301-1.xhtml13
-rw-r--r--layout/svg/crashtests/338312-1.xhtml28
-rw-r--r--layout/svg/crashtests/340083-1.svg9
-rw-r--r--layout/svg/crashtests/340945-1.svg2
-rw-r--r--layout/svg/crashtests/342923-1.html23
-rw-r--r--layout/svg/crashtests/343221-1.xhtml20
-rw-r--r--layout/svg/crashtests/344749-1.svg11
-rw-r--r--layout/svg/crashtests/344887-1.svg18
-rw-r--r--layout/svg/crashtests/344892-1.svg5
-rw-r--r--layout/svg/crashtests/344898-1.svg19
-rw-r--r--layout/svg/crashtests/344904-1.svg19
-rw-r--r--layout/svg/crashtests/345418-1.svg4
-rw-r--r--layout/svg/crashtests/348982-1.xhtml20
-rw-r--r--layout/svg/crashtests/354777-1.xhtml28
-rw-r--r--layout/svg/crashtests/359516-1.svg36
-rw-r--r--layout/svg/crashtests/361015-1.svg33
-rw-r--r--layout/svg/crashtests/361587-1.svg31
-rw-r--r--layout/svg/crashtests/363611-1.xhtml21
-rw-r--r--layout/svg/crashtests/364688-1.svg34
-rw-r--r--layout/svg/crashtests/366956-1.svg61
-rw-r--r--layout/svg/crashtests/366956-2.svg61
-rw-r--r--layout/svg/crashtests/367111-1.svg29
-rw-r--r--layout/svg/crashtests/367368-1.xhtml12
-rw-r--r--layout/svg/crashtests/369233-1.svg33
-rw-r--r--layout/svg/crashtests/369438-1.svg24
-rw-r--r--layout/svg/crashtests/369438-2.svg27
-rw-r--r--layout/svg/crashtests/371463-1.xhtml8
-rw-r--r--layout/svg/crashtests/371563-1.xhtml32
-rw-r--r--layout/svg/crashtests/375775-1.svg23
-rw-r--r--layout/svg/crashtests/378716.svg4
-rw-r--r--layout/svg/crashtests/380691-1.svg4
-rw-r--r--layout/svg/crashtests/384391-1.xhtml20
-rw-r--r--layout/svg/crashtests/384499-1.svg20
-rw-r--r--layout/svg/crashtests/384637-1.svg9
-rw-r--r--layout/svg/crashtests/384728-1.svg21
-rw-r--r--layout/svg/crashtests/385246-1.svg9
-rw-r--r--layout/svg/crashtests/385246-2.svg15
-rw-r--r--layout/svg/crashtests/385552-1.svg4
-rw-r--r--layout/svg/crashtests/385552-2.svg4
-rw-r--r--layout/svg/crashtests/385840-1.svg20
-rw-r--r--layout/svg/crashtests/385852-1.svg34
-rw-r--r--layout/svg/crashtests/386475-1.xhtml24
-rw-r--r--layout/svg/crashtests/386690-1.svg3
-rw-r--r--layout/svg/crashtests/387290-1.svg26
-rw-r--r--layout/svg/crashtests/402408-1.svg32
-rw-r--r--layout/svg/crashtests/404677-1.xhtml9
-rw-r--r--layout/svg/crashtests/409565-1.xhtml3
-rw-r--r--layout/svg/crashtests/420697-1.svg7
-rw-r--r--layout/svg/crashtests/420697-2.svg6
-rw-r--r--layout/svg/crashtests/429774-1.svg29
-rw-r--r--layout/svg/crashtests/441368-1.svg31
-rw-r--r--layout/svg/crashtests/453754-1.svg7
-rw-r--r--layout/svg/crashtests/455314-1.xhtml16
-rw-r--r--layout/svg/crashtests/458453.html24
-rw-r--r--layout/svg/crashtests/459666-1.html7
-rw-r--r--layout/svg/crashtests/459883.xhtml13
-rw-r--r--layout/svg/crashtests/461289-1.svg18
-rw-r--r--layout/svg/crashtests/464374-1.svg15
-rw-r--r--layout/svg/crashtests/466585-1.svg17
-rw-r--r--layout/svg/crashtests/467323-1.svg10
-rw-r--r--layout/svg/crashtests/467498-1.svg12
-rw-r--r--layout/svg/crashtests/470124-1.svg7
-rw-r--r--layout/svg/crashtests/472782-1.svg3
-rw-r--r--layout/svg/crashtests/474700-1.svg1
-rw-r--r--layout/svg/crashtests/475181-1.svg1
-rw-r--r--layout/svg/crashtests/475193-1.html21
-rw-r--r--layout/svg/crashtests/475302-1.svg11
-rw-r--r--layout/svg/crashtests/477935-1.html11
-rw-r--r--layout/svg/crashtests/478128-1.svg15
-rw-r--r--layout/svg/crashtests/478511-1.svg9
-rw-r--r--layout/svg/crashtests/483439-1.svg17
-rw-r--r--layout/svg/crashtests/492186-1.svg6
-rw-r--r--layout/svg/crashtests/508247-1.svg10
-rw-r--r--layout/svg/crashtests/512890-1.svg4
-rw-r--r--layout/svg/crashtests/515288-1.html5
-rw-r--r--layout/svg/crashtests/522394-1.svg12
-rw-r--r--layout/svg/crashtests/522394-2.svg12
-rw-r--r--layout/svg/crashtests/522394-3.svg12
-rw-r--r--layout/svg/crashtests/566216-1.svg19
-rw-r--r--layout/svg/crashtests/587336-1.html9
-rw-r--r--layout/svg/crashtests/590291-1.svg9
-rw-r--r--layout/svg/crashtests/601999-1.html5
-rw-r--r--layout/svg/crashtests/605626-1.svg3
-rw-r--r--layout/svg/crashtests/606914.xhtml1
-rw-r--r--layout/svg/crashtests/610594-1.html6
-rw-r--r--layout/svg/crashtests/610954-1.html1
-rw-r--r--layout/svg/crashtests/612662-1.svg1
-rw-r--r--layout/svg/crashtests/612662-2.svg3
-rw-r--r--layout/svg/crashtests/612736-1.svg19
-rw-r--r--layout/svg/crashtests/612736-2.svg8
-rw-r--r--layout/svg/crashtests/614367-1.svg8
-rw-r--r--layout/svg/crashtests/620034-1.html15
-rw-r--r--layout/svg/crashtests/621598-1.svg16
-rw-r--r--layout/svg/crashtests/648819-1.html6
-rw-r--r--layout/svg/crashtests/655025-1.svg6
-rw-r--r--layout/svg/crashtests/655025-2.svg6
-rw-r--r--layout/svg/crashtests/655025-3.svg9
-rw-r--r--layout/svg/crashtests/657077-1.svg19
-rw-r--r--layout/svg/crashtests/669025-1.svg8
-rw-r--r--layout/svg/crashtests/669025-2.svg8
-rw-r--r--layout/svg/crashtests/682411-1.svg5
-rw-r--r--layout/svg/crashtests/692203-1.svg4
-rw-r--r--layout/svg/crashtests/692203-2.svg4
-rw-r--r--layout/svg/crashtests/693424-1.svg6
-rw-r--r--layout/svg/crashtests/709920-1.svg23
-rw-r--r--layout/svg/crashtests/709920-2.svg23
-rw-r--r--layout/svg/crashtests/713413-1.svg12
-rw-r--r--layout/svg/crashtests/722003-1.svg13
-rw-r--r--layout/svg/crashtests/725918-1.svg4
-rw-r--r--layout/svg/crashtests/732836-1.svg17
-rw-r--r--layout/svg/crashtests/740627-1.svg6
-rw-r--r--layout/svg/crashtests/740627-2.svg6
-rw-r--r--layout/svg/crashtests/743469.svg5
-rw-r--r--layout/svg/crashtests/757704-1.svg17
-rw-r--r--layout/svg/crashtests/757718-1.svg18
-rw-r--r--layout/svg/crashtests/757751-1.svg8
-rw-r--r--layout/svg/crashtests/767056-1.svg21
-rw-r--r--layout/svg/crashtests/767535-1.xhtml22
-rw-r--r--layout/svg/crashtests/768087-1.html4
-rw-r--r--layout/svg/crashtests/768351.svg2
-rw-r--r--layout/svg/crashtests/772313-1.svg1
-rw-r--r--layout/svg/crashtests/778492-1.svg4
-rw-r--r--layout/svg/crashtests/779971-1.svg14
-rw-r--r--layout/svg/crashtests/780764-1.svg18
-rw-r--r--layout/svg/crashtests/780963-1.html27
-rw-r--r--layout/svg/crashtests/782141-1.svg16
-rw-r--r--layout/svg/crashtests/784061-1.svg16
-rw-r--r--layout/svg/crashtests/788831-1.svg5
-rw-r--r--layout/svg/crashtests/789390-1.html1
-rw-r--r--layout/svg/crashtests/790072.svg1
-rw-r--r--layout/svg/crashtests/791826-1.svg14
-rw-r--r--layout/svg/crashtests/803562-1.svg18
-rw-r--r--layout/svg/crashtests/808318-1.svg2
-rw-r--r--layout/svg/crashtests/813420-1.svg14
-rw-r--r--layout/svg/crashtests/841163-1.svg29
-rw-r--r--layout/svg/crashtests/841812-1.svg11
-rw-r--r--layout/svg/crashtests/842009-1.svg5
-rw-r--r--layout/svg/crashtests/842630-1.svg1
-rw-r--r--layout/svg/crashtests/842909-1.svg11
-rw-r--r--layout/svg/crashtests/843072-1.svg11
-rw-r--r--layout/svg/crashtests/843917-1.svg19
-rw-r--r--layout/svg/crashtests/847139-1.svg13
-rw-r--r--layout/svg/crashtests/849688-1.svg11
-rw-r--r--layout/svg/crashtests/849688-2.svg11
-rw-r--r--layout/svg/crashtests/860378-1.svg24
-rw-r--r--layout/svg/crashtests/868904-1.svg17
-rw-r--r--layout/svg/crashtests/873806-1.svg10
-rw-r--r--layout/svg/crashtests/876831-1.svg18
-rw-r--r--layout/svg/crashtests/877029-1.svg10
-rw-r--r--layout/svg/crashtests/880925-1.svg26
-rw-r--r--layout/svg/crashtests/881031-1.svg15
-rw-r--r--layout/svg/crashtests/885608-1.svg13
-rw-r--r--layout/svg/crashtests/890782-1.svg16
-rw-r--r--layout/svg/crashtests/890783-1.svg19
-rw-r--r--layout/svg/crashtests/893510-1.svg5
-rw-r--r--layout/svg/crashtests/895311-1.svg17
-rw-r--r--layout/svg/crashtests/897342-1.svg1
-rw-r--r--layout/svg/crashtests/898909-1.svg11
-rw-r--r--layout/svg/crashtests/898951-1.svg3
-rw-r--r--layout/svg/crashtests/913990.html5
-rw-r--r--layout/svg/crashtests/919371-1.xhtml5
-rw-r--r--layout/svg/crashtests/950324-1.svg3
-rw-r--r--layout/svg/crashtests/951904-1.html43
-rw-r--r--layout/svg/crashtests/952270-1.svg9
-rw-r--r--layout/svg/crashtests/963086-1.svg18
-rw-r--r--layout/svg/crashtests/974746-1.svg9
-rw-r--r--layout/svg/crashtests/975773-1.svg10
-rw-r--r--layout/svg/crashtests/979407-1.svg4
-rw-r--r--layout/svg/crashtests/979407-2.svg4
-rw-r--r--layout/svg/crashtests/993443.svg4
-rw-r--r--layout/svg/crashtests/blob-merging-and-retained-display-list.html62
-rw-r--r--layout/svg/crashtests/conditional-outer-svg-nondirty-reflow-assert.xhtml28
-rw-r--r--layout/svg/crashtests/crashtests.list256
-rw-r--r--layout/svg/crashtests/empty-blob-merging.html48
-rw-r--r--layout/svg/crashtests/extref-test-1-resource.xhtml24
-rw-r--r--layout/svg/crashtests/extref-test-1.xhtml11
-rw-r--r--layout/svg/crashtests/grouping-empty-bounds.html41
-rw-r--r--layout/svg/crashtests/invalid_url.html11
-rw-r--r--layout/svg/crashtests/invalidation-of-opacity-0.html26
-rw-r--r--layout/svg/crashtests/masked-3d-transform.html20
-rw-r--r--layout/svg/crashtests/perspective-invalidation.html9
-rw-r--r--layout/svg/moz.build96
-rw-r--r--layout/svg/svg.css104
-rw-r--r--layout/svg/tests/chrome.ini8
-rw-r--r--layout/svg/tests/file_black_yellow.svg9
-rw-r--r--layout/svg/tests/file_context_fill_fallback_red.html17
-rw-r--r--layout/svg/tests/file_context_fill_fallback_red.svg8
-rw-r--r--layout/svg/tests/file_disabled_iframe.html81
-rw-r--r--layout/svg/tests/file_embed_sizing_both.svg1
-rw-r--r--layout/svg/tests/file_embed_sizing_none.svg1
-rw-r--r--layout/svg/tests/file_embed_sizing_ratio.svg1
-rw-r--r--layout/svg/tests/file_embed_sizing_size.svg1
-rw-r--r--layout/svg/tests/file_filter_crossorigin.svg25
-rw-r--r--layout/svg/tests/file_yellow_black.svg9
-rw-r--r--layout/svg/tests/filters.svg28
-rw-r--r--layout/svg/tests/mochitest.ini28
-rw-r--r--layout/svg/tests/svg_example_script.svg7
-rw-r--r--layout/svg/tests/svg_example_test.html7
-rw-r--r--layout/svg/tests/test_bug1544209.html19
-rw-r--r--layout/svg/tests/test_context_properties_allowed_domains.html95
-rw-r--r--layout/svg/tests/test_disabled.html14
-rw-r--r--layout/svg/tests/test_disabled_chrome.html54
-rw-r--r--layout/svg/tests/test_embed_sizing.html65
-rw-r--r--layout/svg/tests/test_filter_crossorigin.html60
-rw-r--r--layout/svg/tests/test_hover_near_text.html29
-rw-r--r--layout/svg/tests/test_multiple_font_size.html25
-rw-r--r--layout/svg/tests/test_use_tree_cycle.html37
351 files changed, 31034 insertions, 0 deletions
diff --git a/layout/svg/AutoReferenceChainGuard.h b/layout/svg/AutoReferenceChainGuard.h
new file mode 100644
index 0000000000..c25ba0ea70
--- /dev/null
+++ b/layout/svg/AutoReferenceChainGuard.h
@@ -0,0 +1,169 @@
+/* -*- 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 LAYOUT_SVG_AUTOREFERENCECHAINGUARD_H_
+#define LAYOUT_SVG_AUTOREFERENCECHAINGUARD_H_
+
+#include "Element.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ReentrancyGuard.h"
+#include "mozilla/Likely.h"
+#include "nsDebug.h"
+#include "mozilla/dom/Document.h"
+#include "nsIFrame.h"
+
+namespace mozilla {
+
+/**
+ * This helper class helps us to protect against two related issues that can
+ * occur in SVG content: reference loops, and reference chains that we deem to
+ * be too long.
+ *
+ * Some SVG effects can reference another effect of the same type to produce
+ * a chain of effects to be applied (e.g. clipPath), while in other cases it is
+ * possible that while processing an effect of a certain type another effect
+ * of the same type may be encountered indirectly (e.g. pattern). In order to
+ * avoid stack overflow crashes and performance issues we need to impose an
+ * arbitrary limit on the length of the reference chains that SVG content may
+ * try to create. (Some SVG authoring tools have been known to create absurdly
+ * long reference chains. For example, bug 1253590 details a case where Adobe
+ * Illustrator was used to created an SVG with a chain of 5000 clip paths which
+ * could cause us to run out of stack space and crash.)
+ *
+ * This class is intended to be used with the nsIFrame's of SVG effects that
+ * may involve reference chains. To use it add a boolean member, something
+ * like this:
+ *
+ * // Flag used to indicate whether a methods that may reenter due to
+ * // following a reference to another instance is currently executing.
+ * bool mIsBeingProcessed;
+ *
+ * Make sure to initialize the member to false in the class' constructons.
+ *
+ * Then add the following to the top of any methods that may be reentered due
+ * to following a reference:
+ *
+ * static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ *
+ * AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
+ * &sRefChainLengthCounter);
+ * if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ * return; // Break reference chain
+ * }
+ *
+ * Note that mIsBeingProcessed and sRefChainLengthCounter should never be used
+ * by the frame except when it initialize them as indicated above.
+ */
+class MOZ_RAII AutoReferenceChainGuard {
+ static const int16_t sDefaultMaxChainLength = 10; // arbitrary length
+
+ public:
+ static const int16_t noChain = -2;
+
+ /**
+ * @param aFrame The frame for an effect that may involve a reference chain.
+ * @param aFrameInUse The member variable on aFrame that is used to indicate
+ * whether one of aFrame's methods that may involve following a reference
+ * to another effect of the same type is currently being executed.
+ * @param aChainCounter A static variable in the method in which this class
+ * is instantiated that is used to keep track of how many times the method
+ * is reentered (and thus how long the a reference chain is).
+ * @param aMaxChainLength The maximum number of links that are allowed in
+ * a reference chain.
+ */
+ AutoReferenceChainGuard(nsIFrame* aFrame, bool* aFrameInUse,
+ int16_t* aChainCounter,
+ int16_t aMaxChainLength = sDefaultMaxChainLength)
+ : mFrame(aFrame),
+ mFrameInUse(aFrameInUse),
+ mChainCounter(aChainCounter),
+ mMaxChainLength(aMaxChainLength),
+ mBrokeReference(false) {
+ MOZ_ASSERT(aFrame && aFrameInUse && aChainCounter);
+ MOZ_ASSERT(aMaxChainLength > 0);
+ MOZ_ASSERT(*aChainCounter == noChain ||
+ (*aChainCounter >= 0 && *aChainCounter < aMaxChainLength));
+ }
+
+ ~AutoReferenceChainGuard() {
+ if (mBrokeReference) {
+ // We didn't change mFrameInUse or mChainCounter
+ return;
+ }
+
+ *mFrameInUse = false;
+
+ // If we fail this assert then there were more destructor calls than
+ // Reference() calls (a consumer forgot to to call Reference()), or else
+ // someone messed with the variable pointed to by mChainCounter.
+ MOZ_ASSERT(*mChainCounter < mMaxChainLength);
+
+ (*mChainCounter)++;
+
+ if (*mChainCounter == mMaxChainLength) {
+ *mChainCounter = noChain; // reset ready for use next time
+ }
+ }
+
+ /**
+ * Returns true on success (no reference loop/reference chain length is
+ * within the specified limits), else returns false on failure (there is a
+ * reference loop/the reference chain has exceeded the specified limits).
+ * If it returns false then an error message will be reported to the DevTools
+ * console (only once).
+ */
+ [[nodiscard]] bool Reference() {
+ if (MOZ_UNLIKELY(*mFrameInUse)) {
+ mBrokeReference = true;
+ ReportErrorToConsole();
+ return false;
+ }
+
+ if (*mChainCounter == noChain) {
+ // Initialize - we start at aMaxChainLength and decrement towards zero.
+ *mChainCounter = mMaxChainLength;
+ } else {
+ // If we fail this assertion then either a consumer failed to break a
+ // reference loop/chain, or else they called Reference() more than once
+ MOZ_ASSERT(*mChainCounter >= 0);
+
+ if (MOZ_UNLIKELY(*mChainCounter < 1)) {
+ mBrokeReference = true;
+ ReportErrorToConsole();
+ return false;
+ }
+ }
+
+ // We only set these once we know we're returning true.
+ *mFrameInUse = true;
+ (*mChainCounter)--;
+
+ return true;
+ }
+
+ private:
+ void ReportErrorToConsole() {
+ AutoTArray<nsString, 2> params;
+ dom::Element* element = mFrame->GetContent()->AsElement();
+ element->GetTagName(*params.AppendElement());
+ element->GetId(*params.AppendElement());
+ auto doc = mFrame->GetContent()->OwnerDoc();
+ auto warning = *mFrameInUse ? dom::Document::eSVGRefLoop
+ : dom::Document::eSVGRefChainLengthExceeded;
+ doc->WarnOnceAbout(warning, /* asError */ true, params);
+ }
+
+ nsIFrame* mFrame;
+ bool* mFrameInUse;
+ int16_t* mChainCounter;
+ const int16_t mMaxChainLength;
+ bool mBrokeReference;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_AUTOREFERENCECHAINGUARD_H_
diff --git a/layout/svg/CSSClipPathInstance.cpp b/layout/svg/CSSClipPathInstance.cpp
new file mode 100644
index 0000000000..8fd51b2799
--- /dev/null
+++ b/layout/svg/CSSClipPathInstance.cpp
@@ -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/. */
+
+// Main header first:
+#include "CSSClipPathInstance.h"
+
+#include "mozilla/dom/SVGElement.h"
+#include "mozilla/dom/SVGPathData.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/ShapeUtils.h"
+#include "mozilla/SVGUtils.h"
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "nsCSSRendering.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+namespace mozilla {
+
+/* static*/
+void CSSClipPathInstance::ApplyBasicShapeOrPathClip(
+ gfxContext& aContext, nsIFrame* aFrame, const gfxMatrix& aTransform) {
+ RefPtr<Path> path =
+ CreateClipPathForFrame(aContext.GetDrawTarget(), aFrame, aTransform);
+ if (!path) {
+ // This behavior matches |SVGClipPathFrame::ApplyClipPath()|.
+ // https://www.w3.org/TR/css-masking-1/#ClipPathElement:
+ // "An empty clipping path will completely clip away the element that had
+ // the clip-path property applied."
+ aContext.Clip(Rect());
+ return;
+ }
+ aContext.Clip(path);
+}
+
+/* static*/
+RefPtr<Path> CSSClipPathInstance::CreateClipPathForFrame(
+ gfx::DrawTarget* aDt, nsIFrame* aFrame, const gfxMatrix& aTransform) {
+ const auto& clipPathStyle = aFrame->StyleSVGReset()->mClipPath;
+ MOZ_ASSERT(clipPathStyle.IsShape() || clipPathStyle.IsBox() ||
+ clipPathStyle.IsPath(),
+ "This is used with basic-shape, geometry-box, and path() only");
+
+ CSSClipPathInstance instance(aFrame, clipPathStyle);
+
+ return instance.CreateClipPath(aDt, aTransform);
+}
+
+/* static*/
+bool CSSClipPathInstance::HitTestBasicShapeOrPathClip(nsIFrame* aFrame,
+ const gfxPoint& aPoint) {
+ const auto& clipPathStyle = aFrame->StyleSVGReset()->mClipPath;
+ MOZ_ASSERT(!clipPathStyle.IsNone(), "unexpected none value");
+ MOZ_ASSERT(!clipPathStyle.IsUrl(), "unexpected url value");
+
+ CSSClipPathInstance instance(aFrame, clipPathStyle);
+
+ RefPtr<DrawTarget> drawTarget =
+ gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+ RefPtr<Path> path = instance.CreateClipPath(
+ drawTarget, SVGUtils::GetCSSPxToDevPxMatrix(aFrame));
+ float pixelRatio = float(AppUnitsPerCSSPixel()) /
+ aFrame->PresContext()->AppUnitsPerDevPixel();
+ return path && path->ContainsPoint(ToPoint(aPoint) * pixelRatio, Matrix());
+}
+
+/* static */
+Maybe<Rect> CSSClipPathInstance::GetBoundingRectForBasicShapeOrPathClip(
+ nsIFrame* aFrame, const StyleClipPath& aClipPathStyle) {
+ MOZ_ASSERT(aClipPathStyle.IsShape() || aClipPathStyle.IsBox() ||
+ aClipPathStyle.IsPath());
+
+ CSSClipPathInstance instance(aFrame, aClipPathStyle);
+
+ RefPtr<DrawTarget> drawTarget =
+ gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+ RefPtr<Path> path = instance.CreateClipPath(
+ drawTarget, SVGUtils::GetCSSPxToDevPxMatrix(aFrame));
+ return path ? Some(path->GetBounds()) : Nothing();
+}
+
+already_AddRefed<Path> CSSClipPathInstance::CreateClipPath(
+ DrawTarget* aDrawTarget, const gfxMatrix& aTransform) {
+ if (mClipPathStyle.IsPath()) {
+ return CreateClipPathPath(aDrawTarget);
+ }
+
+ nscoord appUnitsPerDevPixel =
+ mTargetFrame->PresContext()->AppUnitsPerDevPixel();
+
+ nsRect r;
+ if (mClipPathStyle.IsBox()) {
+ r = nsLayoutUtils::ComputeGeometryBox(mTargetFrame, mClipPathStyle.AsBox());
+ } else {
+ r = nsLayoutUtils::ComputeGeometryBox(mTargetFrame,
+ mClipPathStyle.AsShape()._1);
+ }
+
+ gfxRect rr(r.x, r.y, r.width, r.height);
+ rr.Scale(1.0 / AppUnitsPerCSSPixel());
+ rr = aTransform.TransformRect(rr);
+ rr.Scale(appUnitsPerDevPixel);
+ rr.Round();
+
+ r = nsRect(int(rr.x), int(rr.y), int(rr.width), int(rr.height));
+
+ if (mClipPathStyle.IsBox()) {
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ AppendRectToPath(builder, NSRectToRect(r, appUnitsPerDevPixel), true);
+ return builder->Finish();
+ }
+
+ MOZ_ASSERT(mClipPathStyle.IsShape());
+
+ r = ToAppUnits(r.ToNearestPixels(appUnitsPerDevPixel), appUnitsPerDevPixel);
+
+ const auto& basicShape = *mClipPathStyle.AsShape()._0;
+ switch (basicShape.tag) {
+ case StyleBasicShape::Tag::Circle:
+ return CreateClipPathCircle(aDrawTarget, r);
+ case StyleBasicShape::Tag::Ellipse:
+ return CreateClipPathEllipse(aDrawTarget, r);
+ case StyleBasicShape::Tag::Polygon:
+ return CreateClipPathPolygon(aDrawTarget, r);
+ case StyleBasicShape::Tag::Inset:
+ return CreateClipPathInset(aDrawTarget, r);
+ break;
+ default:
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected shape type");
+ }
+ // Return an empty Path:
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ return builder->Finish();
+}
+
+already_AddRefed<Path> CSSClipPathInstance::CreateClipPathCircle(
+ DrawTarget* aDrawTarget, const nsRect& aRefBox) {
+ const auto& basicShape = *mClipPathStyle.AsShape()._0;
+
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+
+ nsPoint center =
+ ShapeUtils::ComputeCircleOrEllipseCenter(basicShape, aRefBox);
+ nscoord r = ShapeUtils::ComputeCircleRadius(basicShape, center, aRefBox);
+ nscoord appUnitsPerDevPixel =
+ mTargetFrame->PresContext()->AppUnitsPerDevPixel();
+ builder->Arc(Point(center.x, center.y) / appUnitsPerDevPixel,
+ r / appUnitsPerDevPixel, 0, Float(2 * M_PI));
+ builder->Close();
+ return builder->Finish();
+}
+
+already_AddRefed<Path> CSSClipPathInstance::CreateClipPathEllipse(
+ DrawTarget* aDrawTarget, const nsRect& aRefBox) {
+ const auto& basicShape = *mClipPathStyle.AsShape()._0;
+
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+
+ nsPoint center =
+ ShapeUtils::ComputeCircleOrEllipseCenter(basicShape, aRefBox);
+ nsSize radii = ShapeUtils::ComputeEllipseRadii(basicShape, center, aRefBox);
+ nscoord appUnitsPerDevPixel =
+ mTargetFrame->PresContext()->AppUnitsPerDevPixel();
+ EllipseToBezier(builder.get(),
+ Point(center.x, center.y) / appUnitsPerDevPixel,
+ Size(radii.width, radii.height) / appUnitsPerDevPixel);
+ builder->Close();
+ return builder->Finish();
+}
+
+already_AddRefed<Path> CSSClipPathInstance::CreateClipPathPolygon(
+ DrawTarget* aDrawTarget, const nsRect& aRefBox) {
+ const auto& basicShape = *mClipPathStyle.AsShape()._0;
+ auto fillRule = basicShape.AsPolygon().fill == StyleFillRule::Nonzero
+ ? FillRule::FILL_WINDING
+ : FillRule::FILL_EVEN_ODD;
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(fillRule);
+
+ nsTArray<nsPoint> vertices =
+ ShapeUtils::ComputePolygonVertices(basicShape, aRefBox);
+ if (vertices.IsEmpty()) {
+ MOZ_ASSERT_UNREACHABLE(
+ "ComputePolygonVertices() should've given us some vertices!");
+ } else {
+ nscoord appUnitsPerDevPixel =
+ mTargetFrame->PresContext()->AppUnitsPerDevPixel();
+ builder->MoveTo(NSPointToPoint(vertices[0], appUnitsPerDevPixel));
+ for (size_t i = 1; i < vertices.Length(); ++i) {
+ builder->LineTo(NSPointToPoint(vertices[i], appUnitsPerDevPixel));
+ }
+ }
+ builder->Close();
+ return builder->Finish();
+}
+
+already_AddRefed<Path> CSSClipPathInstance::CreateClipPathInset(
+ DrawTarget* aDrawTarget, const nsRect& aRefBox) {
+ const auto& basicShape = *mClipPathStyle.AsShape()._0;
+
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+
+ nscoord appUnitsPerDevPixel =
+ mTargetFrame->PresContext()->AppUnitsPerDevPixel();
+
+ const nsRect insetRect = ShapeUtils::ComputeInsetRect(basicShape, aRefBox);
+ const Rect insetRectPixels = NSRectToRect(insetRect, appUnitsPerDevPixel);
+ nscoord appUnitsRadii[8];
+
+ if (ShapeUtils::ComputeInsetRadii(basicShape, aRefBox, insetRect,
+ appUnitsRadii)) {
+ RectCornerRadii corners;
+ nsCSSRendering::ComputePixelRadii(appUnitsRadii, appUnitsPerDevPixel,
+ &corners);
+
+ AppendRoundedRectToPath(builder, insetRectPixels, corners, true);
+ } else {
+ AppendRectToPath(builder, insetRectPixels, true);
+ }
+ return builder->Finish();
+}
+
+already_AddRefed<Path> CSSClipPathInstance::CreateClipPathPath(
+ DrawTarget* aDrawTarget) {
+ const auto& path = mClipPathStyle.AsPath();
+
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(
+ path.fill == StyleFillRule::Nonzero ? FillRule::FILL_WINDING
+ : FillRule::FILL_EVEN_ODD);
+ float scale = float(AppUnitsPerCSSPixel()) /
+ mTargetFrame->PresContext()->AppUnitsPerDevPixel();
+ return SVGPathData::BuildPath(path.path._0.AsSpan(), builder,
+ StyleStrokeLinecap::Butt, 0.0, scale);
+}
+
+} // namespace mozilla
diff --git a/layout/svg/CSSClipPathInstance.h b/layout/svg/CSSClipPathInstance.h
new file mode 100644
index 0000000000..90ef52a6fb
--- /dev/null
+++ b/layout/svg/CSSClipPathInstance.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 LAYOUT_SVG_CSSCLIPPATHINSTANCE_H_
+#define LAYOUT_SVG_CSSCLIPPATHINSTANCE_H_
+
+#include "gfxMatrix.h"
+#include "gfxPoint.h"
+#include "mozilla/gfx/2D.h"
+#include "nsRect.h"
+#include "nsStyleStruct.h"
+
+class nsIFrame;
+class gfxContext;
+
+namespace mozilla {
+
+class MOZ_STACK_CLASS CSSClipPathInstance {
+ using DrawTarget = gfx::DrawTarget;
+ using Path = gfx::Path;
+ using Rect = gfx::Rect;
+
+ public:
+ static void ApplyBasicShapeOrPathClip(gfxContext& aContext, nsIFrame* aFrame,
+ const gfxMatrix& aTransform);
+ static RefPtr<Path> CreateClipPathForFrame(gfx::DrawTarget* aDt,
+ nsIFrame* aFrame,
+ const gfxMatrix& aTransform);
+ // aPoint is in CSS pixels.
+ static bool HitTestBasicShapeOrPathClip(nsIFrame* aFrame,
+ const gfxPoint& aPoint);
+
+ static Maybe<Rect> GetBoundingRectForBasicShapeOrPathClip(
+ nsIFrame* aFrame, const StyleClipPath&);
+
+ private:
+ explicit CSSClipPathInstance(nsIFrame* aFrame, const StyleClipPath& aClipPath)
+ : mTargetFrame(aFrame), mClipPathStyle(aClipPath) {}
+
+ already_AddRefed<Path> CreateClipPath(DrawTarget* aDrawTarget,
+ const gfxMatrix& aTransform);
+
+ already_AddRefed<Path> CreateClipPathCircle(DrawTarget* aDrawTarget,
+ const nsRect& aRefBox);
+
+ already_AddRefed<Path> CreateClipPathEllipse(DrawTarget* aDrawTarget,
+ const nsRect& aRefBox);
+
+ already_AddRefed<Path> CreateClipPathPolygon(DrawTarget* aDrawTarget,
+ const nsRect& aRefBox);
+
+ already_AddRefed<Path> CreateClipPathInset(DrawTarget* aDrawTarget,
+ const nsRect& aRefBox);
+
+ already_AddRefed<Path> CreateClipPathPath(DrawTarget* aDrawTarget);
+
+ /**
+ * The frame for the element that is currently being clipped.
+ */
+ nsIFrame* mTargetFrame;
+ const StyleClipPath& mClipPathStyle;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_CSSCLIPPATHINSTANCE_H_
diff --git a/layout/svg/CSSFilterInstance.cpp b/layout/svg/CSSFilterInstance.cpp
new file mode 100644
index 0000000000..02ec3907bd
--- /dev/null
+++ b/layout/svg/CSSFilterInstance.cpp
@@ -0,0 +1,356 @@
+/* -*- 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:
+#include "CSSFilterInstance.h"
+
+// Keep others in (case-insensitive) order:
+#include "FilterDescription.h"
+#include "gfx2DGlue.h"
+#include "gfxUtils.h"
+#include "nsIFrame.h"
+#include "nsStyleStruct.h"
+#include "nsTArray.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+
+static float ClampFactor(float aFactor) {
+ if (aFactor > 1) {
+ return 1;
+ }
+ if (aFactor < 0) {
+ MOZ_ASSERT_UNREACHABLE("A negative value should not have been parsed.");
+ return 0;
+ }
+
+ return aFactor;
+}
+
+CSSFilterInstance::CSSFilterInstance(
+ const StyleFilter& aFilter, nscolor aShadowFallbackColor,
+ const nsIntRect& aTargetBoundsInFilterSpace,
+ const gfxMatrix& aFrameSpaceInCSSPxToFilterSpaceTransform)
+ : mFilter(aFilter),
+ mShadowFallbackColor(aShadowFallbackColor),
+ mTargetBoundsInFilterSpace(aTargetBoundsInFilterSpace),
+ mFrameSpaceInCSSPxToFilterSpaceTransform(
+ aFrameSpaceInCSSPxToFilterSpaceTransform) {}
+
+nsresult CSSFilterInstance::BuildPrimitives(
+ nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ bool aInputIsTainted) {
+ FilterPrimitiveDescription descr =
+ CreatePrimitiveDescription(aPrimitiveDescrs, aInputIsTainted);
+ nsresult result;
+ switch (mFilter.tag) {
+ case StyleFilter::Tag::Blur:
+ result = SetAttributesForBlur(descr);
+ break;
+ case StyleFilter::Tag::Brightness:
+ result = SetAttributesForBrightness(descr);
+ break;
+ case StyleFilter::Tag::Contrast:
+ result = SetAttributesForContrast(descr);
+ break;
+ case StyleFilter::Tag::DropShadow:
+ result = SetAttributesForDropShadow(descr);
+ break;
+ case StyleFilter::Tag::Grayscale:
+ result = SetAttributesForGrayscale(descr);
+ break;
+ case StyleFilter::Tag::HueRotate:
+ result = SetAttributesForHueRotate(descr);
+ break;
+ case StyleFilter::Tag::Invert:
+ result = SetAttributesForInvert(descr);
+ break;
+ case StyleFilter::Tag::Opacity:
+ result = SetAttributesForOpacity(descr);
+ break;
+ case StyleFilter::Tag::Saturate:
+ result = SetAttributesForSaturate(descr);
+ break;
+ case StyleFilter::Tag::Sepia:
+ result = SetAttributesForSepia(descr);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("not a valid CSS filter type");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_FAILED(result)) {
+ return result;
+ }
+
+ // Compute the primitive's bounds now that we've determined its attributes.
+ // Some attributes like blur radius can influence the bounds.
+ SetBounds(descr, aPrimitiveDescrs);
+
+ // Add this primitive to the filter chain.
+ aPrimitiveDescrs.AppendElement(std::move(descr));
+ return NS_OK;
+}
+
+FilterPrimitiveDescription CSSFilterInstance::CreatePrimitiveDescription(
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ bool aInputIsTainted) {
+ FilterPrimitiveDescription descr;
+ int32_t inputIndex = GetLastResultIndex(aPrimitiveDescrs);
+ descr.SetInputPrimitive(0, inputIndex);
+ descr.SetIsTainted(inputIndex < 0 ? aInputIsTainted
+ : aPrimitiveDescrs[inputIndex].IsTainted());
+ descr.SetInputColorSpace(0, ColorSpace::SRGB);
+ descr.SetOutputColorSpace(ColorSpace::SRGB);
+ return descr;
+}
+
+nsresult CSSFilterInstance::SetAttributesForBlur(
+ FilterPrimitiveDescription& aDescr) {
+ const Length& radiusInFrameSpace = mFilter.AsBlur();
+ Size radiusInFilterSpace =
+ BlurRadiusToFilterSpace(radiusInFrameSpace.ToAppUnits());
+ GaussianBlurAttributes atts;
+ atts.mStdDeviation = radiusInFilterSpace;
+ aDescr.Attributes() = AsVariant(atts);
+ return NS_OK;
+}
+
+nsresult CSSFilterInstance::SetAttributesForBrightness(
+ FilterPrimitiveDescription& aDescr) {
+ float value = mFilter.AsBrightness();
+ float intercept = 0.0f;
+ ComponentTransferAttributes atts;
+
+ // Set transfer functions for RGB.
+ atts.mTypes[kChannelROrRGB] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
+ atts.mTypes[kChannelG] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R;
+ atts.mTypes[kChannelB] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R;
+ float slopeIntercept[2];
+ slopeIntercept[kComponentTransferSlopeIndex] = value;
+ slopeIntercept[kComponentTransferInterceptIndex] = intercept;
+ atts.mValues[kChannelROrRGB].AppendElements(slopeIntercept, 2);
+
+ atts.mTypes[kChannelA] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY;
+
+ aDescr.Attributes() = AsVariant(std::move(atts));
+ return NS_OK;
+}
+
+nsresult CSSFilterInstance::SetAttributesForContrast(
+ FilterPrimitiveDescription& aDescr) {
+ float value = mFilter.AsContrast();
+ float intercept = -(0.5 * value) + 0.5;
+ ComponentTransferAttributes atts;
+
+ // Set transfer functions for RGB.
+ atts.mTypes[kChannelROrRGB] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_LINEAR;
+ atts.mTypes[kChannelG] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R;
+ atts.mTypes[kChannelB] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R;
+ float slopeIntercept[2];
+ slopeIntercept[kComponentTransferSlopeIndex] = value;
+ slopeIntercept[kComponentTransferInterceptIndex] = intercept;
+ atts.mValues[kChannelROrRGB].AppendElements(slopeIntercept, 2);
+
+ atts.mTypes[kChannelA] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY;
+
+ aDescr.Attributes() = AsVariant(std::move(atts));
+ return NS_OK;
+}
+
+nsresult CSSFilterInstance::SetAttributesForDropShadow(
+ FilterPrimitiveDescription& aDescr) {
+ const auto& shadow = mFilter.AsDropShadow();
+
+ DropShadowAttributes atts;
+
+ // Set drop shadow blur radius.
+ Size radiusInFilterSpace = BlurRadiusToFilterSpace(shadow.blur.ToAppUnits());
+ atts.mStdDeviation = radiusInFilterSpace;
+
+ // Set offset.
+ IntPoint offsetInFilterSpace = OffsetToFilterSpace(
+ shadow.horizontal.ToAppUnits(), shadow.vertical.ToAppUnits());
+ atts.mOffset = offsetInFilterSpace;
+
+ // Set color. If unspecified, use the CSS color property.
+ nscolor shadowColor = shadow.color.CalcColor(mShadowFallbackColor);
+ atts.mColor = ToAttributeColor(shadowColor);
+
+ aDescr.Attributes() = AsVariant(std::move(atts));
+ return NS_OK;
+}
+
+nsresult CSSFilterInstance::SetAttributesForGrayscale(
+ FilterPrimitiveDescription& aDescr) {
+ ColorMatrixAttributes atts;
+ // Set color matrix type.
+ atts.mType = (uint32_t)SVG_FECOLORMATRIX_TYPE_SATURATE;
+
+ // Set color matrix values.
+ float value = 1 - ClampFactor(mFilter.AsGrayscale());
+ atts.mValues.AppendElements(&value, 1);
+
+ aDescr.Attributes() = AsVariant(std::move(atts));
+ return NS_OK;
+}
+
+nsresult CSSFilterInstance::SetAttributesForHueRotate(
+ FilterPrimitiveDescription& aDescr) {
+ ColorMatrixAttributes atts;
+ // Set color matrix type.
+ atts.mType = (uint32_t)SVG_FECOLORMATRIX_TYPE_HUE_ROTATE;
+
+ // Set color matrix values.
+ float value = mFilter.AsHueRotate().ToDegrees();
+ atts.mValues.AppendElements(&value, 1);
+
+ aDescr.Attributes() = AsVariant(std::move(atts));
+ return NS_OK;
+}
+
+nsresult CSSFilterInstance::SetAttributesForInvert(
+ FilterPrimitiveDescription& aDescr) {
+ ComponentTransferAttributes atts;
+ float value = ClampFactor(mFilter.AsInvert());
+
+ // Set transfer functions for RGB.
+ float invertTableValues[2];
+ invertTableValues[0] = value;
+ invertTableValues[1] = 1 - value;
+
+ // Set transfer functions for RGB.
+ atts.mTypes[kChannelROrRGB] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_TABLE;
+ atts.mTypes[kChannelG] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R;
+ atts.mTypes[kChannelB] = (uint8_t)SVG_FECOMPONENTTRANSFER_SAME_AS_R;
+ atts.mValues[kChannelROrRGB].AppendElements(invertTableValues, 2);
+
+ atts.mTypes[kChannelA] = (uint8_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY;
+
+ aDescr.Attributes() = AsVariant(std::move(atts));
+ return NS_OK;
+}
+
+nsresult CSSFilterInstance::SetAttributesForOpacity(
+ FilterPrimitiveDescription& aDescr) {
+ OpacityAttributes atts;
+ float value = ClampFactor(mFilter.AsOpacity());
+
+ atts.mOpacity = value;
+ aDescr.Attributes() = AsVariant(std::move(atts));
+ return NS_OK;
+}
+
+nsresult CSSFilterInstance::SetAttributesForSaturate(
+ FilterPrimitiveDescription& aDescr) {
+ ColorMatrixAttributes atts;
+ // Set color matrix type.
+ atts.mType = (uint32_t)SVG_FECOLORMATRIX_TYPE_SATURATE;
+
+ // Set color matrix values.
+ float value = mFilter.AsSaturate();
+ atts.mValues.AppendElements(&value, 1);
+
+ aDescr.Attributes() = AsVariant(std::move(atts));
+ return NS_OK;
+}
+
+nsresult CSSFilterInstance::SetAttributesForSepia(
+ FilterPrimitiveDescription& aDescr) {
+ ColorMatrixAttributes atts;
+ // Set color matrix type.
+ atts.mType = (uint32_t)SVG_FECOLORMATRIX_TYPE_SEPIA;
+
+ // Set color matrix values.
+ float value = ClampFactor(mFilter.AsSepia());
+ atts.mValues.AppendElements(&value, 1);
+
+ aDescr.Attributes() = AsVariant(std::move(atts));
+ return NS_OK;
+}
+
+Size CSSFilterInstance::BlurRadiusToFilterSpace(nscoord aRadiusInFrameSpace) {
+ float radiusInFrameSpaceInCSSPx =
+ nsPresContext::AppUnitsToFloatCSSPixels(aRadiusInFrameSpace);
+
+ // Convert the radius to filter space.
+ Size radiusInFilterSpace(radiusInFrameSpaceInCSSPx,
+ radiusInFrameSpaceInCSSPx);
+ // Narrow the scale factors. They will only be used with types containing
+ // floating point types.
+ auto frameSpaceInCSSPxToFilterSpaceScale =
+ mFrameSpaceInCSSPxToFilterSpaceTransform.ScaleFactors()
+ .ConvertTo<float>();
+ radiusInFilterSpace =
+ radiusInFilterSpace * frameSpaceInCSSPxToFilterSpaceScale;
+
+ // Check the radius limits.
+ if (radiusInFilterSpace.width < 0 || radiusInFilterSpace.height < 0) {
+ MOZ_ASSERT_UNREACHABLE(
+ "we shouldn't have parsed a negative radius in the "
+ "style");
+ return Size();
+ }
+
+ Float maxStdDeviation = (Float)kMaxStdDeviation;
+ radiusInFilterSpace.width =
+ std::min(radiusInFilterSpace.width, maxStdDeviation);
+ radiusInFilterSpace.height =
+ std::min(radiusInFilterSpace.height, maxStdDeviation);
+
+ return radiusInFilterSpace;
+}
+
+IntPoint CSSFilterInstance::OffsetToFilterSpace(nscoord aXOffsetInFrameSpace,
+ nscoord aYOffsetInFrameSpace) {
+ gfxPoint offsetInFilterSpace(
+ nsPresContext::AppUnitsToFloatCSSPixels(aXOffsetInFrameSpace),
+ nsPresContext::AppUnitsToFloatCSSPixels(aYOffsetInFrameSpace));
+
+ // Convert the radius to filter space.
+ auto frameSpaceInCSSPxToFilterSpaceScale =
+ mFrameSpaceInCSSPxToFilterSpaceTransform.ScaleFactors();
+ offsetInFilterSpace.x *= frameSpaceInCSSPxToFilterSpaceScale.xScale;
+ offsetInFilterSpace.y *= frameSpaceInCSSPxToFilterSpaceScale.yScale;
+
+ return IntPoint(int32_t(offsetInFilterSpace.x),
+ int32_t(offsetInFilterSpace.y));
+}
+
+sRGBColor CSSFilterInstance::ToAttributeColor(nscolor aColor) {
+ return sRGBColor(NS_GET_R(aColor) / 255.0, NS_GET_G(aColor) / 255.0,
+ NS_GET_B(aColor) / 255.0, NS_GET_A(aColor) / 255.0);
+}
+
+int32_t CSSFilterInstance::GetLastResultIndex(
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) {
+ uint32_t numPrimitiveDescrs = aPrimitiveDescrs.Length();
+ return !numPrimitiveDescrs
+ ? FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic
+ : numPrimitiveDescrs - 1;
+}
+
+void CSSFilterInstance::SetBounds(
+ FilterPrimitiveDescription& aDescr,
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) {
+ int32_t inputIndex = GetLastResultIndex(aPrimitiveDescrs);
+ nsIntRect inputBounds =
+ (inputIndex < 0) ? mTargetBoundsInFilterSpace
+ : aPrimitiveDescrs[inputIndex].PrimitiveSubregion();
+
+ nsTArray<nsIntRegion> inputExtents;
+ inputExtents.AppendElement(inputBounds);
+
+ nsIntRegion outputExtents =
+ FilterSupport::PostFilterExtentsForPrimitive(aDescr, inputExtents);
+ IntRect outputBounds = outputExtents.GetBounds();
+
+ aDescr.SetPrimitiveSubregion(outputBounds);
+ aDescr.SetFilterSpaceBounds(outputBounds);
+}
+
+} // namespace mozilla
diff --git a/layout/svg/CSSFilterInstance.h b/layout/svg/CSSFilterInstance.h
new file mode 100644
index 0000000000..cdf21ee2bf
--- /dev/null
+++ b/layout/svg/CSSFilterInstance.h
@@ -0,0 +1,146 @@
+/* -*- 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 LAYOUT_SVG_CSSFILTERINSTANCE_H_
+#define LAYOUT_SVG_CSSFILTERINSTANCE_H_
+
+#include "FilterSupport.h"
+#include "gfxMatrix.h"
+#include "gfxRect.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Types.h"
+#include "nsColor.h"
+#include "mozilla/ServoStyleConsts.h"
+
+namespace mozilla {
+
+/**
+ * This class helps FilterInstance build its filter graph. It turns a CSS
+ * filter function (e.g. blur(3px)) from the style system into a
+ * FilterPrimitiveDescription connected to the filter graph.
+ */
+class CSSFilterInstance {
+ using sRGBColor = gfx::sRGBColor;
+ using FilterPrimitiveDescription = gfx::FilterPrimitiveDescription;
+ using IntPoint = gfx::IntPoint;
+ using Size = gfx::Size;
+
+ public:
+ /**
+ * @param aFilter The CSS filter from the style system. This class stores
+ * aFilter by reference, so callers should avoid modifying or deleting
+ * aFilter during the lifetime of CSSFilterInstance.
+ * @param aShadowFallbackColor The color that should be used for
+ * drop-shadow() filters that don't specify a shadow color.
+ * @param aTargetBoundsInFilterSpace The pre-filter ink overflow rect of
+ * the frame being filtered, in filter space.
+ * @param aFrameSpaceInCSSPxToFilterSpaceTransform The transformation from
+ * the filtered element's frame space in CSS pixels to filter space.
+ */
+ CSSFilterInstance(const StyleFilter& aFilter, nscolor aShadowFallbackColor,
+ const nsIntRect& aTargetBoundsInFilterSpace,
+ const gfxMatrix& aFrameSpaceInCSSPxToFilterSpaceTransform);
+
+ /**
+ * Creates at least one new FilterPrimitiveDescription based on the filter
+ * from the style system. Appends the new FilterPrimitiveDescription(s) to the
+ * aPrimitiveDescrs list.
+ * aInputIsTainted describes whether the input to this filter is tainted, i.e.
+ * whether it contains security-sensitive content. This is needed to propagate
+ * taintedness to the FilterPrimitive that take tainted inputs. Something
+ * being tainted means that it contains security sensitive content. The input
+ * to this filter is the previous filter's output, i.e. the last element in
+ * aPrimitiveDescrs, or the SourceGraphic input if this is the first filter in
+ * the filter chain.
+ */
+ nsresult BuildPrimitives(
+ nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ bool aInputIsTainted);
+
+ private:
+ /**
+ * Returns a new FilterPrimitiveDescription with its basic properties set up.
+ * See the comment above BuildPrimitives for the meaning of aInputIsTainted.
+ */
+ FilterPrimitiveDescription CreatePrimitiveDescription(
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ bool aInputIsTainted);
+
+ /**
+ * Sets aDescr's attributes using the style info in mFilter.
+ */
+ nsresult SetAttributesForBlur(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForBrightness(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForContrast(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForDropShadow(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForGrayscale(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForHueRotate(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForInvert(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForOpacity(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForSaturate(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForSepia(FilterPrimitiveDescription& aDescr);
+
+ /**
+ * Returns the index of the last result in the aPrimitiveDescrs, which we'll
+ * use as the input to this CSS filter.
+ */
+ int32_t GetLastResultIndex(
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs);
+
+ /**
+ * Sets aDescr's filter region and primitive subregion to appropriate values
+ * based on this CSS filter's input and its attributes. For example, a CSS
+ * blur filter will have bounds equal to its input bounds, inflated by the
+ * blur extents.
+ */
+ void SetBounds(FilterPrimitiveDescription& aDescr,
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs);
+
+ /**
+ * Converts an nscolor to a Color, suitable for use as a
+ * FilterPrimitiveDescription attribute.
+ */
+ sRGBColor ToAttributeColor(nscolor aColor);
+
+ /**
+ * Converts a blur radius in frame space to filter space.
+ */
+ Size BlurRadiusToFilterSpace(nscoord aRadiusInFrameSpace);
+
+ /**
+ * Converts a point defined by a pair of nscoord x, y coordinates from frame
+ * space to filter space.
+ */
+ IntPoint OffsetToFilterSpace(nscoord aXOffsetInFrameSpace,
+ nscoord aYOffsetInFrameSpace);
+
+ /**
+ * The CSS filter originally from the style system.
+ */
+ const StyleFilter& mFilter;
+
+ /**
+ * The color that should be used for drop-shadow() filters that don't
+ * specify a shadow color.
+ */
+ nscolor mShadowFallbackColor;
+
+ /**
+ * The pre-filter overflow rect of the frame being filtered, in filter space.
+ * Used for input bounds if this CSS filter is the first in the filter chain.
+ */
+ nsIntRect mTargetBoundsInFilterSpace;
+
+ /**
+ * The transformation from the filtered element's frame space in CSS pixels to
+ * filter space. Used to transform style values to filter space.
+ */
+ gfxMatrix mFrameSpaceInCSSPxToFilterSpaceTransform;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_CSSFILTERINSTANCE_H_
diff --git a/layout/svg/FilterInstance.cpp b/layout/svg/FilterInstance.cpp
new file mode 100644
index 0000000000..bdc6e9b3bc
--- /dev/null
+++ b/layout/svg/FilterInstance.cpp
@@ -0,0 +1,903 @@
+/* -*- 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:
+#include "FilterInstance.h"
+
+// MFBT headers next:
+#include "mozilla/UniquePtr.h"
+
+// Keep others in (case-insensitive) order:
+#include "FilterSupport.h"
+#include "ImgDrawResult.h"
+#include "SVGContentUtils.h"
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+
+#include "gfxUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/gfx/Filters.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/PatternHelpers.h"
+#include "mozilla/ISVGDisplayableFrame.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/SVGFilterInstance.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/Document.h"
+#include "nsLayoutUtils.h"
+#include "CSSFilterInstance.h"
+#include "SVGIntegrationUtils.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+namespace mozilla {
+
+FilterDescription FilterInstance::GetFilterDescription(
+ nsIContent* aFilteredElement, Span<const StyleFilter> aFilterChain,
+ bool aFilterInputIsTainted, const UserSpaceMetrics& aMetrics,
+ const gfxRect& aBBox,
+ nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages) {
+ gfxMatrix identity;
+ FilterInstance instance(nullptr, aFilteredElement, aMetrics, aFilterChain,
+ aFilterInputIsTainted, nullptr, identity, nullptr,
+ nullptr, nullptr, &aBBox);
+ if (!instance.IsInitialized()) {
+ return FilterDescription();
+ }
+ return instance.ExtractDescriptionAndAdditionalImages(aOutAdditionalImages);
+}
+
+static UniquePtr<UserSpaceMetrics> UserSpaceMetricsForFrame(nsIFrame* aFrame) {
+ if (auto* element = SVGElement::FromNodeOrNull(aFrame->GetContent())) {
+ return MakeUnique<SVGElementMetrics>(element);
+ }
+ return MakeUnique<NonSVGFrameUserSpaceMetrics>(aFrame);
+}
+
+void FilterInstance::PaintFilteredFrame(
+ nsIFrame* aFilteredFrame, Span<const StyleFilter> aFilterChain,
+ gfxContext* aCtx, const SVGFilterPaintCallback& aPaintCallback,
+ const nsRegion* aDirtyArea, imgDrawingParams& aImgParams, float aOpacity) {
+ UniquePtr<UserSpaceMetrics> metrics =
+ UserSpaceMetricsForFrame(aFilteredFrame);
+
+ gfxContextMatrixAutoSaveRestore autoSR(aCtx);
+ auto scaleFactors = aCtx->CurrentMatrixDouble().ScaleFactors();
+ if (scaleFactors.xScale == 0 || scaleFactors.yScale == 0) {
+ return;
+ }
+
+ gfxMatrix scaleMatrix(scaleFactors.xScale, 0.0f, 0.0f, scaleFactors.yScale,
+ 0.0f, 0.0f);
+
+ gfxMatrix reverseScaleMatrix = scaleMatrix;
+ DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
+ MOZ_ASSERT(invertible);
+
+ gfxMatrix scaleMatrixInDevUnits =
+ scaleMatrix * SVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame);
+
+ // Hardcode InputIsTainted to true because we don't want JS to be able to
+ // read the rendered contents of aFilteredFrame.
+ FilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+ *metrics, aFilterChain, /* InputIsTainted */ true,
+ aPaintCallback, scaleMatrixInDevUnits, aDirtyArea,
+ nullptr, nullptr, nullptr);
+ if (instance.IsInitialized()) {
+ // Pull scale vector out of aCtx's transform, put all scale factors, which
+ // includes css and css-to-dev-px scale, into scaleMatrixInDevUnits.
+ aCtx->SetMatrixDouble(reverseScaleMatrix * aCtx->CurrentMatrixDouble());
+
+ instance.Render(aCtx, aImgParams, aOpacity);
+ } else {
+ // Render the unfiltered contents.
+ aPaintCallback(*aCtx, aImgParams, nullptr, nullptr);
+ }
+}
+
+static mozilla::wr::ComponentTransferFuncType FuncTypeToWr(uint8_t aFuncType) {
+ MOZ_ASSERT(aFuncType != SVG_FECOMPONENTTRANSFER_SAME_AS_R);
+ switch (aFuncType) {
+ case SVG_FECOMPONENTTRANSFER_TYPE_TABLE:
+ return mozilla::wr::ComponentTransferFuncType::Table;
+ case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE:
+ return mozilla::wr::ComponentTransferFuncType::Discrete;
+ case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR:
+ return mozilla::wr::ComponentTransferFuncType::Linear;
+ case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA:
+ return mozilla::wr::ComponentTransferFuncType::Gamma;
+ case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY:
+ default:
+ return mozilla::wr::ComponentTransferFuncType::Identity;
+ }
+ MOZ_ASSERT_UNREACHABLE("all func types not handled?");
+ return mozilla::wr::ComponentTransferFuncType::Identity;
+}
+
+bool FilterInstance::BuildWebRenderFilters(nsIFrame* aFilteredFrame,
+ Span<const StyleFilter> aFilters,
+ WrFiltersHolder& aWrFilters,
+ bool& aInitialized) {
+ bool status = BuildWebRenderFiltersImpl(aFilteredFrame, aFilters, aWrFilters,
+ aInitialized);
+ if (!status) {
+ aFilteredFrame->PresContext()->Document()->SetUseCounter(
+ eUseCounter_custom_WrFilterFallback);
+ }
+
+ return status;
+}
+
+bool FilterInstance::BuildWebRenderFiltersImpl(nsIFrame* aFilteredFrame,
+ Span<const StyleFilter> aFilters,
+ WrFiltersHolder& aWrFilters,
+ bool& aInitialized) {
+ aWrFilters.filters.Clear();
+ aWrFilters.filter_datas.Clear();
+ aWrFilters.values.Clear();
+
+ UniquePtr<UserSpaceMetrics> metrics =
+ UserSpaceMetricsForFrame(aFilteredFrame);
+
+ // TODO: simply using an identity matrix here, was pulling the scale from a
+ // gfx context for the non-wr path.
+ gfxMatrix scaleMatrix;
+ gfxMatrix scaleMatrixInDevUnits =
+ scaleMatrix * SVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame);
+
+ // Hardcode inputIsTainted to true because we don't want JS to be able to
+ // read the rendered contents of aFilteredFrame.
+ bool inputIsTainted = true;
+ FilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+ *metrics, aFilters, inputIsTainted, nullptr,
+ scaleMatrixInDevUnits, nullptr, nullptr, nullptr,
+ nullptr);
+
+ if (!instance.IsInitialized()) {
+ aInitialized = false;
+ return true;
+ }
+
+ // If there are too many filters to render, then just pretend that we
+ // succeeded, and don't render any of them.
+ if (instance.mFilterDescription.mPrimitives.Length() >
+ StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
+ return true;
+ }
+
+ Maybe<IntRect> finalClip;
+ bool srgb = true;
+ // We currently apply the clip on the stacking context after applying filters,
+ // but primitive subregions imply clipping after each filter and not just the
+ // end of the chain. For some types of filter it doesn't matter, but for those
+ // which sample outside of the location of the destination pixel like blurs,
+ // only clipping after could produce incorrect results, so we bail out in this
+ // case.
+ // We can lift this restriction once we have added support for primitive
+ // subregions to WebRender's filters.
+ for (uint32_t i = 0; i < instance.mFilterDescription.mPrimitives.Length();
+ i++) {
+ const auto& primitive = instance.mFilterDescription.mPrimitives[i];
+
+ // WebRender only supports filters with one input.
+ if (primitive.NumberOfInputs() != 1) {
+ return false;
+ }
+ // The first primitive must have the source graphic as the input, all
+ // other primitives must have the prior primitive as the input, otherwise
+ // it's not supported by WebRender.
+ if (i == 0) {
+ if (primitive.InputPrimitiveIndex(0) !=
+ FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic) {
+ return false;
+ }
+ } else if (primitive.InputPrimitiveIndex(0) != int32_t(i - 1)) {
+ return false;
+ }
+
+ bool previousSrgb = srgb;
+ bool primNeedsSrgb = primitive.InputColorSpace(0) == gfx::ColorSpace::SRGB;
+ if (srgb && !primNeedsSrgb) {
+ aWrFilters.filters.AppendElement(wr::FilterOp::SrgbToLinear());
+ } else if (!srgb && primNeedsSrgb) {
+ aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb());
+ }
+ srgb = primitive.OutputColorSpace() == gfx::ColorSpace::SRGB;
+
+ const PrimitiveAttributes& attr = primitive.Attributes();
+
+ bool filterIsNoop = false;
+
+ if (attr.is<OpacityAttributes>()) {
+ float opacity = attr.as<OpacityAttributes>().mOpacity;
+ aWrFilters.filters.AppendElement(wr::FilterOp::Opacity(
+ wr::PropertyBinding<float>::Value(opacity), opacity));
+ } else if (attr.is<ColorMatrixAttributes>()) {
+ const ColorMatrixAttributes& attributes =
+ attr.as<ColorMatrixAttributes>();
+
+ float transposed[20];
+ if (gfx::ComputeColorMatrix(attributes, transposed)) {
+ float matrix[20] = {
+ transposed[0], transposed[5], transposed[10], transposed[15],
+ transposed[1], transposed[6], transposed[11], transposed[16],
+ transposed[2], transposed[7], transposed[12], transposed[17],
+ transposed[3], transposed[8], transposed[13], transposed[18],
+ transposed[4], transposed[9], transposed[14], transposed[19]};
+
+ aWrFilters.filters.AppendElement(wr::FilterOp::ColorMatrix(matrix));
+ } else {
+ filterIsNoop = true;
+ }
+ } else if (attr.is<GaussianBlurAttributes>()) {
+ if (finalClip) {
+ // There's a clip that needs to apply before the blur filter, but
+ // WebRender only lets us apply the clip at the end of the filter
+ // chain. Clipping after a blur is not equivalent to clipping before
+ // a blur, so bail out.
+ return false;
+ }
+
+ const GaussianBlurAttributes& blur = attr.as<GaussianBlurAttributes>();
+
+ const Size& stdDev = blur.mStdDeviation;
+ if (stdDev.width != 0.0 || stdDev.height != 0.0) {
+ aWrFilters.filters.AppendElement(
+ wr::FilterOp::Blur(stdDev.width, stdDev.height));
+ } else {
+ filterIsNoop = true;
+ }
+ } else if (attr.is<DropShadowAttributes>()) {
+ if (finalClip) {
+ // We have to bail out for the same reason we would with a blur filter.
+ return false;
+ }
+
+ const DropShadowAttributes& shadow = attr.as<DropShadowAttributes>();
+
+ const Size& stdDev = shadow.mStdDeviation;
+ if (stdDev.width != stdDev.height) {
+ return false;
+ }
+
+ sRGBColor color = shadow.mColor;
+ if (!primNeedsSrgb) {
+ color = sRGBColor(gsRGBToLinearRGBMap[uint8_t(color.r * 255)],
+ gsRGBToLinearRGBMap[uint8_t(color.g * 255)],
+ gsRGBToLinearRGBMap[uint8_t(color.b * 255)], color.a);
+ }
+ wr::Shadow wrShadow;
+ wrShadow.offset = {shadow.mOffset.x, shadow.mOffset.y};
+ wrShadow.color = wr::ToColorF(ToDeviceColor(color));
+ wrShadow.blur_radius = stdDev.width;
+ wr::FilterOp filterOp = wr::FilterOp::DropShadow(wrShadow);
+
+ aWrFilters.filters.AppendElement(filterOp);
+ } else if (attr.is<ComponentTransferAttributes>()) {
+ const ComponentTransferAttributes& attributes =
+ attr.as<ComponentTransferAttributes>();
+
+ size_t numValues =
+ attributes.mValues[0].Length() + attributes.mValues[1].Length() +
+ attributes.mValues[2].Length() + attributes.mValues[3].Length();
+ if (numValues > 1024) {
+ // Depending on how the wr shaders are implemented we may need to
+ // limit the total number of values.
+ return false;
+ }
+
+ wr::FilterOp filterOp = {wr::FilterOp::Tag::ComponentTransfer};
+ wr::WrFilterData filterData;
+ aWrFilters.values.AppendElement(nsTArray<float>());
+ nsTArray<float>* values =
+ &aWrFilters.values[aWrFilters.values.Length() - 1];
+ values->SetCapacity(numValues);
+
+ filterData.funcR_type = FuncTypeToWr(attributes.mTypes[0]);
+ size_t R_startindex = values->Length();
+ values->AppendElements(attributes.mValues[0]);
+ filterData.R_values_count = attributes.mValues[0].Length();
+
+ size_t indexToUse =
+ attributes.mTypes[1] == SVG_FECOMPONENTTRANSFER_SAME_AS_R ? 0 : 1;
+ filterData.funcG_type = FuncTypeToWr(attributes.mTypes[indexToUse]);
+ size_t G_startindex = values->Length();
+ values->AppendElements(attributes.mValues[indexToUse]);
+ filterData.G_values_count = attributes.mValues[indexToUse].Length();
+
+ indexToUse =
+ attributes.mTypes[2] == SVG_FECOMPONENTTRANSFER_SAME_AS_R ? 0 : 2;
+ filterData.funcB_type = FuncTypeToWr(attributes.mTypes[indexToUse]);
+ size_t B_startindex = values->Length();
+ values->AppendElements(attributes.mValues[indexToUse]);
+ filterData.B_values_count = attributes.mValues[indexToUse].Length();
+
+ filterData.funcA_type = FuncTypeToWr(attributes.mTypes[3]);
+ size_t A_startindex = values->Length();
+ values->AppendElements(attributes.mValues[3]);
+ filterData.A_values_count = attributes.mValues[3].Length();
+
+ filterData.R_values =
+ filterData.R_values_count > 0 ? &((*values)[R_startindex]) : nullptr;
+ filterData.G_values =
+ filterData.G_values_count > 0 ? &((*values)[G_startindex]) : nullptr;
+ filterData.B_values =
+ filterData.B_values_count > 0 ? &((*values)[B_startindex]) : nullptr;
+ filterData.A_values =
+ filterData.A_values_count > 0 ? &((*values)[A_startindex]) : nullptr;
+
+ aWrFilters.filters.AppendElement(filterOp);
+ aWrFilters.filter_datas.AppendElement(filterData);
+ } else {
+ return false;
+ }
+
+ if (filterIsNoop && aWrFilters.filters.Length() > 0 &&
+ (aWrFilters.filters.LastElement().tag ==
+ wr::FilterOp::Tag::SrgbToLinear ||
+ aWrFilters.filters.LastElement().tag ==
+ wr::FilterOp::Tag::LinearToSrgb)) {
+ // We pushed a color space conversion filter in prevision of applying
+ // another filter which turned out to be a no-op, so the conversion is
+ // unnecessary. Remove it from the filter list.
+ // This is both an optimization and a way to pass the wptest
+ // css/filter-effects/filter-scale-001.html for which the needless
+ // sRGB->linear->no-op->sRGB roundtrip introduces a slight error and we
+ // cannot add fuzziness to the test.
+ Unused << aWrFilters.filters.PopLastElement();
+ srgb = previousSrgb;
+ }
+
+ if (!filterIsNoop) {
+ if (finalClip.isNothing()) {
+ finalClip = Some(primitive.PrimitiveSubregion());
+ } else {
+ finalClip =
+ Some(primitive.PrimitiveSubregion().Intersect(finalClip.value()));
+ }
+ }
+ }
+
+ if (!srgb) {
+ aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb());
+ }
+
+ if (finalClip) {
+ aWrFilters.post_filters_clip =
+ Some(instance.FilterSpaceToFrameSpace(finalClip.value()));
+ }
+ return true;
+}
+
+nsRegion FilterInstance::GetPreFilterNeededArea(
+ nsIFrame* aFilteredFrame, const nsRegion& aPostFilterDirtyRegion) {
+ gfxMatrix tm = SVGUtils::GetCanvasTM(aFilteredFrame);
+ auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
+ UniquePtr<UserSpaceMetrics> metrics =
+ UserSpaceMetricsForFrame(aFilteredFrame);
+ // Hardcode InputIsTainted to true because we don't want JS to be able to
+ // read the rendered contents of aFilteredFrame.
+ FilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+ *metrics, filterChain, /* InputIsTainted */ true,
+ nullptr, tm, &aPostFilterDirtyRegion);
+ if (!instance.IsInitialized()) {
+ return nsRect();
+ }
+
+ // Now we can ask the instance to compute the area of the source
+ // that's needed.
+ return instance.ComputeSourceNeededRect();
+}
+
+Maybe<nsRect> FilterInstance::GetPostFilterBounds(
+ nsIFrame* aFilteredFrame, const gfxRect* aOverrideBBox,
+ const nsRect* aPreFilterBounds) {
+ MOZ_ASSERT(!aFilteredFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ||
+ !aFilteredFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
+ "Non-display SVG do not maintain ink overflow rects");
+
+ nsRegion preFilterRegion;
+ nsRegion* preFilterRegionPtr = nullptr;
+ if (aPreFilterBounds) {
+ preFilterRegion = *aPreFilterBounds;
+ preFilterRegionPtr = &preFilterRegion;
+ }
+
+ gfxMatrix tm = SVGUtils::GetCanvasTM(aFilteredFrame);
+ auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
+ UniquePtr<UserSpaceMetrics> metrics =
+ UserSpaceMetricsForFrame(aFilteredFrame);
+ // Hardcode InputIsTainted to true because we don't want JS to be able to
+ // read the rendered contents of aFilteredFrame.
+ FilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+ *metrics, filterChain, /* InputIsTainted */ true,
+ nullptr, tm, nullptr, preFilterRegionPtr,
+ aPreFilterBounds, aOverrideBBox);
+ if (!instance.IsInitialized()) {
+ return Nothing();
+ }
+
+ return Some(instance.ComputePostFilterExtents());
+}
+
+FilterInstance::FilterInstance(
+ nsIFrame* aTargetFrame, nsIContent* aTargetContent,
+ const UserSpaceMetrics& aMetrics, Span<const StyleFilter> aFilterChain,
+ bool aFilterInputIsTainted, const SVGFilterPaintCallback& aPaintCallback,
+ const gfxMatrix& aPaintTransform, const nsRegion* aPostFilterDirtyRegion,
+ const nsRegion* aPreFilterDirtyRegion,
+ const nsRect* aPreFilterInkOverflowRectOverride,
+ const gfxRect* aOverrideBBox)
+ : mTargetFrame(aTargetFrame),
+ mTargetContent(aTargetContent),
+ mMetrics(aMetrics),
+ mPaintCallback(aPaintCallback),
+ mPaintTransform(aPaintTransform),
+ mInitialized(false) {
+ if (aOverrideBBox) {
+ mTargetBBox = *aOverrideBBox;
+ } else {
+ MOZ_ASSERT(mTargetFrame,
+ "Need to supply a frame when there's no aOverrideBBox");
+ mTargetBBox =
+ SVGUtils::GetBBox(mTargetFrame, SVGUtils::eUseFrameBoundsForOuterSVG |
+ SVGUtils::eBBoxIncludeFillGeometry);
+ }
+
+ // Compute user space to filter space transforms.
+ if (!ComputeUserSpaceToFilterSpaceScale()) {
+ return;
+ }
+
+ if (!ComputeTargetBBoxInFilterSpace()) {
+ return;
+ }
+
+ // Get various transforms:
+ gfxMatrix filterToUserSpace(mFilterSpaceToUserSpaceScale.xScale, 0.0f, 0.0f,
+ mFilterSpaceToUserSpaceScale.yScale, 0.0f, 0.0f);
+
+ mFilterSpaceToFrameSpaceInCSSPxTransform =
+ filterToUserSpace * GetUserSpaceToFrameSpaceInCSSPxTransform();
+ // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
+ mFrameSpaceInCSSPxToFilterSpaceTransform =
+ mFilterSpaceToFrameSpaceInCSSPxTransform;
+ mFrameSpaceInCSSPxToFilterSpaceTransform.Invert();
+
+ nsIntRect targetBounds;
+ if (aPreFilterInkOverflowRectOverride) {
+ targetBounds = FrameSpaceToFilterSpace(aPreFilterInkOverflowRectOverride);
+ } else if (mTargetFrame) {
+ nsRect preFilterVOR = mTargetFrame->PreEffectsInkOverflowRect();
+ targetBounds = FrameSpaceToFilterSpace(&preFilterVOR);
+ }
+ mTargetBounds.UnionRect(mTargetBBoxInFilterSpace, targetBounds);
+
+ // Build the filter graph.
+ if (NS_FAILED(
+ BuildPrimitives(aFilterChain, aTargetFrame, aFilterInputIsTainted))) {
+ return;
+ }
+
+ // Convert the passed in rects from frame space to filter space:
+ mPostFilterDirtyRegion = FrameSpaceToFilterSpace(aPostFilterDirtyRegion);
+ mPreFilterDirtyRegion = FrameSpaceToFilterSpace(aPreFilterDirtyRegion);
+
+ mInitialized = true;
+}
+
+bool FilterInstance::ComputeTargetBBoxInFilterSpace() {
+ gfxRect targetBBoxInFilterSpace = UserSpaceToFilterSpace(mTargetBBox);
+ targetBBoxInFilterSpace.RoundOut();
+
+ return gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace,
+ &mTargetBBoxInFilterSpace);
+}
+
+bool FilterInstance::ComputeUserSpaceToFilterSpaceScale() {
+ if (mTargetFrame) {
+ mUserSpaceToFilterSpaceScale = mPaintTransform.ScaleFactors();
+ if (mUserSpaceToFilterSpaceScale.xScale <= 0.0f ||
+ mUserSpaceToFilterSpaceScale.yScale <= 0.0f) {
+ // Nothing should be rendered.
+ return false;
+ }
+ } else {
+ mUserSpaceToFilterSpaceScale = MatrixScalesDouble();
+ }
+
+ mFilterSpaceToUserSpaceScale =
+ MatrixScalesDouble(1.0f / mUserSpaceToFilterSpaceScale.xScale,
+ 1.0f / mUserSpaceToFilterSpaceScale.yScale);
+
+ return true;
+}
+
+gfxRect FilterInstance::UserSpaceToFilterSpace(
+ const gfxRect& aUserSpaceRect) const {
+ gfxRect filterSpaceRect = aUserSpaceRect;
+ filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale);
+ return filterSpaceRect;
+}
+
+gfxRect FilterInstance::FilterSpaceToUserSpace(
+ const gfxRect& aFilterSpaceRect) const {
+ gfxRect userSpaceRect = aFilterSpaceRect;
+ userSpaceRect.Scale(mFilterSpaceToUserSpaceScale);
+ return userSpaceRect;
+}
+
+nsresult FilterInstance::BuildPrimitives(Span<const StyleFilter> aFilterChain,
+ nsIFrame* aTargetFrame,
+ bool aFilterInputIsTainted) {
+ nsTArray<FilterPrimitiveDescription> primitiveDescriptions;
+
+ for (uint32_t i = 0; i < aFilterChain.Length(); i++) {
+ bool inputIsTainted = primitiveDescriptions.IsEmpty()
+ ? aFilterInputIsTainted
+ : primitiveDescriptions.LastElement().IsTainted();
+ nsresult rv = BuildPrimitivesForFilter(
+ aFilterChain[i], aTargetFrame, inputIsTainted, primitiveDescriptions);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ mFilterDescription = FilterDescription(std::move(primitiveDescriptions));
+
+ return NS_OK;
+}
+
+nsresult FilterInstance::BuildPrimitivesForFilter(
+ const StyleFilter& aFilter, nsIFrame* aTargetFrame, bool aInputIsTainted,
+ nsTArray<FilterPrimitiveDescription>& aPrimitiveDescriptions) {
+ NS_ASSERTION(mUserSpaceToFilterSpaceScale.xScale > 0.0f &&
+ mFilterSpaceToUserSpaceScale.yScale > 0.0f,
+ "scale factors between spaces should be positive values");
+
+ if (aFilter.IsUrl()) {
+ // Build primitives for an SVG filter.
+ SVGFilterInstance svgFilterInstance(aFilter, aTargetFrame, mTargetContent,
+ mMetrics, mTargetBBox,
+ mUserSpaceToFilterSpaceScale);
+ if (!svgFilterInstance.IsInitialized()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return svgFilterInstance.BuildPrimitives(aPrimitiveDescriptions,
+ mInputImages, aInputIsTainted);
+ }
+
+ // Build primitives for a CSS filter.
+
+ // If we don't have a frame, use opaque black for shadows with unspecified
+ // shadow colors.
+ nscolor shadowFallbackColor =
+ mTargetFrame ? mTargetFrame->StyleText()->mColor.ToColor()
+ : NS_RGB(0, 0, 0);
+
+ CSSFilterInstance cssFilterInstance(aFilter, shadowFallbackColor,
+ mTargetBounds,
+ mFrameSpaceInCSSPxToFilterSpaceTransform);
+ return cssFilterInstance.BuildPrimitives(aPrimitiveDescriptions,
+ aInputIsTainted);
+}
+
+static void UpdateNeededBounds(const nsIntRegion& aRegion, nsIntRect& aBounds) {
+ aBounds = aRegion.GetBounds();
+
+ bool overflow;
+ IntSize surfaceSize =
+ SVGUtils::ConvertToSurfaceSize(SizeDouble(aBounds.Size()), &overflow);
+ if (overflow) {
+ aBounds.SizeTo(surfaceSize);
+ }
+}
+
+void FilterInstance::ComputeNeededBoxes() {
+ if (mFilterDescription.mPrimitives.IsEmpty()) {
+ return;
+ }
+
+ nsIntRegion sourceGraphicNeededRegion;
+ nsIntRegion fillPaintNeededRegion;
+ nsIntRegion strokePaintNeededRegion;
+
+ FilterSupport::ComputeSourceNeededRegions(
+ mFilterDescription, mPostFilterDirtyRegion, sourceGraphicNeededRegion,
+ fillPaintNeededRegion, strokePaintNeededRegion);
+
+ sourceGraphicNeededRegion.And(sourceGraphicNeededRegion, mTargetBounds);
+
+ UpdateNeededBounds(sourceGraphicNeededRegion, mSourceGraphic.mNeededBounds);
+ UpdateNeededBounds(fillPaintNeededRegion, mFillPaint.mNeededBounds);
+ UpdateNeededBounds(strokePaintNeededRegion, mStrokePaint.mNeededBounds);
+}
+
+void FilterInstance::BuildSourcePaint(SourceInfo* aSource,
+ imgDrawingParams& aImgParams) {
+ MOZ_ASSERT(mTargetFrame);
+ nsIntRect neededRect = aSource->mNeededBounds;
+ if (neededRect.IsEmpty()) {
+ return;
+ }
+
+ RefPtr<DrawTarget> offscreenDT =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ neededRect.Size(), SurfaceFormat::B8G8R8A8);
+ if (!offscreenDT || !offscreenDT->IsValid()) {
+ return;
+ }
+
+ RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
+ MOZ_ASSERT(ctx); // already checked the draw target above
+ gfxContextAutoSaveRestore saver(ctx);
+
+ ctx->SetMatrixDouble(mPaintTransform *
+ gfxMatrix::Translation(-neededRect.TopLeft()));
+ GeneralPattern pattern;
+ if (aSource == &mFillPaint) {
+ SVGUtils::MakeFillPatternFor(mTargetFrame, ctx, &pattern, aImgParams);
+ } else if (aSource == &mStrokePaint) {
+ SVGUtils::MakeStrokePatternFor(mTargetFrame, ctx, &pattern, aImgParams);
+ }
+
+ if (pattern.GetPattern()) {
+ offscreenDT->FillRect(
+ ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))), pattern);
+ }
+
+ aSource->mSourceSurface = offscreenDT->Snapshot();
+ aSource->mSurfaceRect = neededRect;
+}
+
+void FilterInstance::BuildSourcePaints(imgDrawingParams& aImgParams) {
+ if (!mFillPaint.mNeededBounds.IsEmpty()) {
+ BuildSourcePaint(&mFillPaint, aImgParams);
+ }
+
+ if (!mStrokePaint.mNeededBounds.IsEmpty()) {
+ BuildSourcePaint(&mStrokePaint, aImgParams);
+ }
+}
+
+void FilterInstance::BuildSourceImage(DrawTarget* aDest,
+ imgDrawingParams& aImgParams,
+ FilterNode* aFilter, FilterNode* aSource,
+ const Rect& aSourceRect) {
+ MOZ_ASSERT(mTargetFrame);
+
+ nsIntRect neededRect = mSourceGraphic.mNeededBounds;
+ if (neededRect.IsEmpty()) {
+ return;
+ }
+
+ RefPtr<DrawTarget> offscreenDT;
+ SurfaceFormat format = SurfaceFormat::B8G8R8A8;
+ if (aDest->CanCreateSimilarDrawTarget(neededRect.Size(), format)) {
+ offscreenDT = aDest->CreateSimilarDrawTargetForFilter(
+ neededRect.Size(), format, aFilter, aSource, aSourceRect, Point(0, 0));
+ }
+ if (!offscreenDT || !offscreenDT->IsValid()) {
+ return;
+ }
+
+ gfxRect r = FilterSpaceToUserSpace(ThebesRect(neededRect));
+ r.RoundOut();
+ nsIntRect dirty;
+ if (!gfxUtils::GfxRectToIntRect(r, &dirty)) {
+ return;
+ }
+
+ // SVG graphics paint to device space, so we need to set an initial device
+ // space to filter space transform on the gfxContext that SourceGraphic
+ // and SourceAlpha will paint to.
+ //
+ // (In theory it would be better to minimize error by having filtered SVG
+ // graphics temporarily paint to user space when painting the sources and
+ // only set a user space to filter space transform on the gfxContext
+ // (since that would eliminate the transform multiplications from user
+ // space to device space and back again). However, that would make the
+ // code more complex while being hard to get right without introducing
+ // subtle bugs, and in practice it probably makes no real difference.)
+ RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
+ MOZ_ASSERT(ctx); // already checked the draw target above
+ gfxMatrix devPxToCssPxTM = SVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
+ DebugOnly<bool> invertible = devPxToCssPxTM.Invert();
+ MOZ_ASSERT(invertible);
+ ctx->SetMatrixDouble(devPxToCssPxTM * mPaintTransform *
+ gfxMatrix::Translation(-neededRect.TopLeft()));
+
+ auto imageFlags = aImgParams.imageFlags;
+ if (mTargetFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ // We're coming from a mask or pattern instance. Patterns
+ // are painted into a separate surface and it seems we can't
+ // handle the differently sized surface that might be returned
+ // with FLAG_HIGH_QUALITY_SCALING
+ imageFlags &= ~imgIContainer::FLAG_HIGH_QUALITY_SCALING;
+ }
+ imgDrawingParams imgParams(imageFlags);
+ mPaintCallback(*ctx, imgParams, &mPaintTransform, &dirty);
+ aImgParams.result = imgParams.result;
+
+ mSourceGraphic.mSourceSurface = offscreenDT->Snapshot();
+ mSourceGraphic.mSurfaceRect = neededRect;
+}
+
+void FilterInstance::Render(gfxContext* aCtx, imgDrawingParams& aImgParams,
+ float aOpacity) {
+ MOZ_ASSERT(mTargetFrame, "Need a frame for rendering");
+
+ if (mFilterDescription.mPrimitives.IsEmpty()) {
+ // An filter without any primitive. Treat it as success and paint nothing.
+ return;
+ }
+
+ nsIntRect filterRect =
+ mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds());
+ if (filterRect.IsEmpty() || mPaintTransform.IsSingular()) {
+ return;
+ }
+
+ gfxContextMatrixAutoSaveRestore autoSR(aCtx);
+ aCtx->SetMatrix(
+ aCtx->CurrentMatrix().PreTranslate(filterRect.x, filterRect.y));
+
+ ComputeNeededBoxes();
+
+ Rect renderRect = IntRectToRect(filterRect);
+ RefPtr<DrawTarget> dt = aCtx->GetDrawTarget();
+
+ MOZ_ASSERT(dt);
+ if (!dt->IsValid()) {
+ return;
+ }
+
+ BuildSourcePaints(aImgParams);
+ RefPtr<FilterNode> sourceGraphic, fillPaint, strokePaint;
+ if (mFillPaint.mSourceSurface) {
+ fillPaint = FilterWrappers::ForSurface(dt, mFillPaint.mSourceSurface,
+ mFillPaint.mSurfaceRect.TopLeft());
+ }
+ if (mStrokePaint.mSourceSurface) {
+ strokePaint = FilterWrappers::ForSurface(
+ dt, mStrokePaint.mSourceSurface, mStrokePaint.mSurfaceRect.TopLeft());
+ }
+
+ // We make the sourceGraphic filter but don't set its inputs until after so
+ // that we can make the sourceGraphic size depend on the filter chain
+ sourceGraphic = dt->CreateFilter(FilterType::TRANSFORM);
+ if (sourceGraphic) {
+ // Make sure we set the translation before calling BuildSourceImage
+ // so that CreateSimilarDrawTargetForFilter works properly
+ IntPoint offset = mSourceGraphic.mNeededBounds.TopLeft();
+ sourceGraphic->SetAttribute(ATT_TRANSFORM_MATRIX,
+ Matrix::Translation(offset.x, offset.y));
+ }
+
+ RefPtr<FilterNode> resultFilter = FilterNodeGraphFromDescription(
+ dt, mFilterDescription, renderRect, sourceGraphic,
+ mSourceGraphic.mSurfaceRect, fillPaint, strokePaint, mInputImages);
+
+ if (!resultFilter) {
+ gfxWarning() << "Filter is NULL.";
+ return;
+ }
+
+ BuildSourceImage(dt, aImgParams, resultFilter, sourceGraphic, renderRect);
+ if (sourceGraphic) {
+ if (mSourceGraphic.mSourceSurface) {
+ sourceGraphic->SetInput(IN_TRANSFORM_IN, mSourceGraphic.mSourceSurface);
+ } else {
+ RefPtr<FilterNode> clear = FilterWrappers::Clear(aCtx->GetDrawTarget());
+ sourceGraphic->SetInput(IN_TRANSFORM_IN, clear);
+ }
+ }
+
+ dt->DrawFilter(resultFilter, renderRect, Point(0, 0), DrawOptions(aOpacity));
+}
+
+nsRegion FilterInstance::ComputePostFilterDirtyRegion() {
+ if (mPreFilterDirtyRegion.IsEmpty() ||
+ mFilterDescription.mPrimitives.IsEmpty()) {
+ return nsRegion();
+ }
+
+ nsIntRegion resultChangeRegion = FilterSupport::ComputeResultChangeRegion(
+ mFilterDescription, mPreFilterDirtyRegion, nsIntRegion(), nsIntRegion());
+ return FilterSpaceToFrameSpace(resultChangeRegion);
+}
+
+nsRect FilterInstance::ComputePostFilterExtents() {
+ if (mFilterDescription.mPrimitives.IsEmpty()) {
+ return nsRect();
+ }
+
+ nsIntRegion postFilterExtents = FilterSupport::ComputePostFilterExtents(
+ mFilterDescription, mTargetBounds);
+ return FilterSpaceToFrameSpace(postFilterExtents.GetBounds());
+}
+
+nsRect FilterInstance::ComputeSourceNeededRect() {
+ ComputeNeededBoxes();
+ return FilterSpaceToFrameSpace(mSourceGraphic.mNeededBounds);
+}
+
+nsIntRect FilterInstance::OutputFilterSpaceBounds() const {
+ uint32_t numPrimitives = mFilterDescription.mPrimitives.Length();
+ if (numPrimitives <= 0) {
+ return nsIntRect();
+ }
+
+ return mFilterDescription.mPrimitives[numPrimitives - 1].PrimitiveSubregion();
+}
+
+nsIntRect FilterInstance::FrameSpaceToFilterSpace(const nsRect* aRect) const {
+ nsIntRect rect = OutputFilterSpaceBounds();
+ if (aRect) {
+ if (aRect->IsEmpty()) {
+ return nsIntRect();
+ }
+ gfxRect rectInCSSPx =
+ nsLayoutUtils::RectToGfxRect(*aRect, AppUnitsPerCSSPixel());
+ gfxRect rectInFilterSpace =
+ mFrameSpaceInCSSPxToFilterSpaceTransform.TransformBounds(rectInCSSPx);
+ rectInFilterSpace.RoundOut();
+ nsIntRect intRect;
+ if (gfxUtils::GfxRectToIntRect(rectInFilterSpace, &intRect)) {
+ rect = intRect;
+ }
+ }
+ return rect;
+}
+
+nsRect FilterInstance::FilterSpaceToFrameSpace(const nsIntRect& aRect) const {
+ if (aRect.IsEmpty()) {
+ return nsRect();
+ }
+ gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height);
+ r = mFilterSpaceToFrameSpaceInCSSPxTransform.TransformBounds(r);
+ // nsLayoutUtils::RoundGfxRectToAppRect rounds out.
+ return nsLayoutUtils::RoundGfxRectToAppRect(r, AppUnitsPerCSSPixel());
+}
+
+nsIntRegion FilterInstance::FrameSpaceToFilterSpace(
+ const nsRegion* aRegion) const {
+ if (!aRegion) {
+ return OutputFilterSpaceBounds();
+ }
+ nsIntRegion result;
+ for (auto iter = aRegion->RectIter(); !iter.Done(); iter.Next()) {
+ // FrameSpaceToFilterSpace rounds out, so this works.
+ nsRect rect = iter.Get();
+ result.Or(result, FrameSpaceToFilterSpace(&rect));
+ }
+ return result;
+}
+
+nsRegion FilterInstance::FilterSpaceToFrameSpace(
+ const nsIntRegion& aRegion) const {
+ nsRegion result;
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ // FilterSpaceToFrameSpace rounds out, so this works.
+ result.Or(result, FilterSpaceToFrameSpace(iter.Get()));
+ }
+ return result;
+}
+
+gfxMatrix FilterInstance::GetUserSpaceToFrameSpaceInCSSPxTransform() const {
+ if (!mTargetFrame) {
+ return gfxMatrix();
+ }
+ return gfxMatrix::Translation(
+ -SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mTargetFrame));
+}
+
+} // namespace mozilla
diff --git a/layout/svg/FilterInstance.h b/layout/svg/FilterInstance.h
new file mode 100644
index 0000000000..de59f2aaf7
--- /dev/null
+++ b/layout/svg/FilterInstance.h
@@ -0,0 +1,405 @@
+/* -*- 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 LAYOUT_SVG_FILTERINSTANCE_H_
+#define LAYOUT_SVG_FILTERINSTANCE_H_
+
+#include "gfxMatrix.h"
+#include "gfxPoint.h"
+#include "gfxRect.h"
+#include "nsCOMPtr.h"
+#include "FilterDescription.h"
+#include "nsHashKeys.h"
+#include "nsPoint.h"
+#include "nsRect.h"
+#include "nsSize.h"
+#include "nsTArray.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "mozilla/SVGIntegrationUtils.h"
+
+class gfxContext;
+class nsIContent;
+class nsIFrame;
+struct WrFiltersHolder;
+
+namespace mozilla {
+
+namespace dom {
+class UserSpaceMetrics;
+} // namespace dom
+
+namespace image {
+struct imgDrawingParams;
+}
+
+/**
+ * This class performs all filter processing.
+ *
+ * We build a graph of the filter image data flow, essentially
+ * converting the filter graph to SSA. This lets us easily propagate
+ * analysis data (such as bounding-boxes) over the filter primitive graph.
+ *
+ * Definition of "filter space": filter space is a coordinate system that is
+ * aligned with the user space of the filtered element, with its origin located
+ * at the top left of the filter region, and with one unit equal in size to one
+ * pixel of the offscreen surface into which the filter output would/will be
+ * painted.
+ *
+ * The definition of "filter region" can be found here:
+ * http://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion
+ */
+class FilterInstance {
+ using IntRect = gfx::IntRect;
+ using SourceSurface = gfx::SourceSurface;
+ using DrawTarget = gfx::DrawTarget;
+ using FilterPrimitiveDescription = gfx::FilterPrimitiveDescription;
+ using FilterDescription = gfx::FilterDescription;
+ using UserSpaceMetrics = dom::UserSpaceMetrics;
+ using imgDrawingParams = image::imgDrawingParams;
+ using SVGFilterPaintCallback = SVGIntegrationUtils::SVGFilterPaintCallback;
+
+ public:
+ /**
+ * Create a FilterDescription for the supplied filter. All coordinates in
+ * the description are in filter space.
+ * @param aFilterInputIsTainted Describes whether the SourceImage /
+ * SourceAlpha input is tainted. This affects whether feDisplacementMap
+ * will respect the filter input as its map input, and it affects the
+ * IsTainted() state on the filter primitives in the FilterDescription.
+ * "Tainted" is a term from the filters spec and means security-sensitive
+ * content, i.e. pixels that JS should not be able to read in any way.
+ * @param aOutAdditionalImages Will contain additional images needed to
+ * render the filter (from feImage primitives).
+ * @return A FilterDescription describing the filter.
+ */
+ static FilterDescription GetFilterDescription(
+ nsIContent* aFilteredElement, Span<const StyleFilter> aFilterChain,
+ bool aFilterInputIsTainted, const UserSpaceMetrics& aMetrics,
+ const gfxRect& aBBox,
+ nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages);
+
+ /**
+ * Paint the given filtered frame.
+ * @param aDirtyArea The area than needs to be painted, in aFilteredFrame's
+ * frame space (i.e. relative to its origin, the top-left corner of its
+ * border box).
+ */
+ static void PaintFilteredFrame(nsIFrame* aFilteredFrame,
+ Span<const StyleFilter> aFilterChain,
+ gfxContext* aCtx,
+ const SVGFilterPaintCallback& aPaintCallback,
+ const nsRegion* aDirtyArea,
+ imgDrawingParams& aImgParams,
+ float aOpacity = 1.0f);
+
+ /**
+ * Returns the post-filter area that could be dirtied when the given
+ * pre-filter area of aFilteredFrame changes.
+ * @param aPreFilterDirtyRegion The pre-filter area of aFilteredFrame that
+ * has changed, relative to aFilteredFrame, in app units.
+ */
+ static nsRegion GetPostFilterDirtyArea(nsIFrame* aFilteredFrame,
+ const nsRegion& aPreFilterDirtyRegion);
+
+ /**
+ * Returns the pre-filter area that is needed from aFilteredFrame when the
+ * given post-filter area needs to be repainted.
+ * @param aPostFilterDirtyRegion The post-filter area that is dirty, relative
+ * to aFilteredFrame, in app units.
+ */
+ static nsRegion GetPreFilterNeededArea(
+ nsIFrame* aFilteredFrame, const nsRegion& aPostFilterDirtyRegion);
+
+ /**
+ * Returns the post-filter ink overflow rect (paint bounds) of
+ * aFilteredFrame.
+ * @param aOverrideBBox A user space rect, in user units, that should be used
+ * as aFilteredFrame's bbox ('bbox' is a specific SVG term), if non-null.
+ * @param aPreFilterBounds The pre-filter ink overflow rect of
+ * aFilteredFrame, if non-null.
+ */
+ static Maybe<nsRect> GetPostFilterBounds(
+ nsIFrame* aFilteredFrame, const gfxRect* aOverrideBBox = nullptr,
+ const nsRect* aPreFilterBounds = nullptr);
+
+ /**
+ * Try to build WebRender filters for a frame if the filters applied to it are
+ * supported. aInitialized is set to true if the filter has been initialized
+ * and false otherwise (e.g. a bad url). If aInitialized is false the filter
+ * the filter contents should not be drawn.
+ */
+ static bool BuildWebRenderFilters(
+ nsIFrame* aFilteredFrame,
+ mozilla::Span<const mozilla::StyleFilter> aFilters,
+ WrFiltersHolder& aWrFilters, bool& aInitialized);
+
+ private:
+ /**
+ * @param aTargetFrame The frame of the filtered element under consideration,
+ * may be null.
+ * @param aTargetContent The filtered element itself.
+ * @param aMetrics The metrics to resolve SVG lengths against.
+ * @param aFilterChain The list of filters to apply.
+ * @param aFilterInputIsTainted Describes whether the SourceImage /
+ * SourceAlpha input is tainted. This affects whether feDisplacementMap
+ * will respect the filter input as its map input.
+ * @param aPaintCallback [optional] The callback that Render() should use to
+ * paint. Only required if you will call Render().
+ * @param aPaintTransform The transform to apply to convert to
+ * aTargetFrame's SVG user space. Only used when painting.
+ * @param aPostFilterDirtyRegion [optional] The post-filter area
+ * that has to be repainted, in app units. Only required if you will
+ * call ComputeSourceNeededRect() or Render().
+ * @param aPreFilterDirtyRegion [optional] The pre-filter area of
+ * the filtered element that changed, in app units. Only required if you
+ * will call ComputePostFilterDirtyRegion().
+ * @param aPreFilterInkOverflowRectOverride [optional] Use a different
+ * ink overflow rect for the target element.
+ * @param aOverrideBBox [optional] Use a different SVG bbox for the target
+ * element. Must be non-null if aTargetFrame is null.
+ */
+ FilterInstance(
+ nsIFrame* aTargetFrame, nsIContent* aTargetContent,
+ const UserSpaceMetrics& aMetrics, Span<const StyleFilter> aFilterChain,
+ bool aFilterInputIsTainted,
+ const SVGIntegrationUtils::SVGFilterPaintCallback& aPaintCallback,
+ const gfxMatrix& aPaintTransform,
+ const nsRegion* aPostFilterDirtyRegion = nullptr,
+ const nsRegion* aPreFilterDirtyRegion = nullptr,
+ const nsRect* aPreFilterInkOverflowRectOverride = nullptr,
+ const gfxRect* aOverrideBBox = nullptr);
+
+ static bool BuildWebRenderFiltersImpl(
+ nsIFrame* aFilteredFrame,
+ mozilla::Span<const mozilla::StyleFilter> aFilters,
+ WrFiltersHolder& aWrFilters, bool& aInitialized);
+
+ /**
+ * Returns true if the filter instance was created successfully.
+ */
+ bool IsInitialized() const { return mInitialized; }
+
+ /**
+ * Draws the filter output into aDrawTarget. The area that
+ * needs to be painted must have been specified before calling this method
+ * by passing it as the aPostFilterDirtyRegion argument to the
+ * FilterInstance constructor.
+ */
+ void Render(gfxContext* aCtx, imgDrawingParams& aImgParams,
+ float aOpacity = 1.0f);
+
+ const FilterDescription& ExtractDescriptionAndAdditionalImages(
+ nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages) {
+ aOutAdditionalImages = std::move(mInputImages);
+ return mFilterDescription;
+ }
+
+ /**
+ * Sets the aPostFilterDirtyRegion outparam to the post-filter area in frame
+ * space that would be dirtied by mTargetFrame when a given
+ * pre-filter area of mTargetFrame is dirtied. The pre-filter area must have
+ * been specified before calling this method by passing it as the
+ * aPreFilterDirtyRegion argument to the FilterInstance constructor.
+ */
+ nsRegion ComputePostFilterDirtyRegion();
+
+ /**
+ * Sets the aPostFilterExtents outparam to the post-filter bounds in frame
+ * space for the whole filter output. This is not necessarily equivalent to
+ * the area that would be dirtied in the result when the entire pre-filter
+ * area is dirtied, because some filter primitives can generate output
+ * without any input.
+ */
+ nsRect ComputePostFilterExtents();
+
+ /**
+ * Sets the aDirty outparam to the pre-filter bounds in frame space of the
+ * area of mTargetFrame that is needed in order to paint the filtered output
+ * for a given post-filter dirtied area. The post-filter area must have been
+ * specified before calling this method by passing it as the
+ * aPostFilterDirtyRegion argument to the FilterInstance constructor.
+ */
+ nsRect ComputeSourceNeededRect();
+
+ struct SourceInfo {
+ // Specifies which parts of the source need to be rendered.
+ // Set by ComputeNeededBoxes().
+ nsIntRect mNeededBounds;
+
+ // The surface that contains the input rendering.
+ // Set by BuildSourceImage / BuildSourcePaint.
+ RefPtr<SourceSurface> mSourceSurface;
+
+ // The position and size of mSourceSurface in filter space.
+ // Set by BuildSourceImage / BuildSourcePaint.
+ IntRect mSurfaceRect;
+ };
+
+ /**
+ * Creates a SourceSurface for either the FillPaint or StrokePaint graph
+ * nodes
+ */
+ void BuildSourcePaint(SourceInfo* aSource, imgDrawingParams& aImgParams);
+
+ /**
+ * Creates a SourceSurface for either the FillPaint and StrokePaint graph
+ * nodes, fills its contents and assigns it to mFillPaint.mSourceSurface and
+ * mStrokePaint.mSourceSurface respectively.
+ */
+ void BuildSourcePaints(imgDrawingParams& aImgParams);
+
+ /**
+ * Creates the SourceSurface for the SourceGraphic graph node, paints its
+ * contents, and assigns it to mSourceGraphic.mSourceSurface.
+ */
+ void BuildSourceImage(DrawTarget* aDest, imgDrawingParams& aImgParams,
+ mozilla::gfx::FilterNode* aFilter,
+ mozilla::gfx::FilterNode* aSource,
+ const mozilla::gfx::Rect& aSourceRect);
+
+ /**
+ * Build the list of FilterPrimitiveDescriptions that describes the filter's
+ * filter primitives and their connections. This populates
+ * mPrimitiveDescriptions and mInputImages. aFilterInputIsTainted describes
+ * whether the SourceGraphic is tainted.
+ */
+ nsresult BuildPrimitives(Span<const StyleFilter> aFilterChain,
+ nsIFrame* aTargetFrame, bool aFilterInputIsTainted);
+
+ /**
+ * Add to the list of FilterPrimitiveDescriptions for a particular SVG
+ * reference filter or CSS filter. This populates mPrimitiveDescriptions and
+ * mInputImages. aInputIsTainted describes whether the input to aFilter is
+ * tainted.
+ */
+ nsresult BuildPrimitivesForFilter(
+ const StyleFilter& aFilter, nsIFrame* aTargetFrame, bool aInputIsTainted,
+ nsTArray<FilterPrimitiveDescription>& aPrimitiveDescriptions);
+
+ /**
+ * Computes the filter space bounds of the areas that we actually *need* from
+ * the filter sources, based on the value of mPostFilterDirtyRegion.
+ * This sets mNeededBounds on the corresponding SourceInfo structs.
+ */
+ void ComputeNeededBoxes();
+
+ /**
+ * Returns the output bounds of the final FilterPrimitiveDescription.
+ */
+ nsIntRect OutputFilterSpaceBounds() const;
+
+ /**
+ * Compute the scale factors between user space and filter space.
+ */
+ bool ComputeUserSpaceToFilterSpaceScale();
+
+ /**
+ * Transform a rect between user space and filter space.
+ */
+ gfxRect UserSpaceToFilterSpace(const gfxRect& aUserSpace) const;
+ gfxRect FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const;
+
+ /**
+ * Converts an nsRect or an nsRegion that is relative to a filtered frame's
+ * origin (i.e. the top-left corner of its border box) into filter space,
+ * rounding out.
+ * Returns the entire filter region if aRect / aRegion is null, or if the
+ * result is too large to be stored in an nsIntRect.
+ */
+ nsIntRect FrameSpaceToFilterSpace(const nsRect* aRect) const;
+ nsIntRegion FrameSpaceToFilterSpace(const nsRegion* aRegion) const;
+
+ /**
+ * Converts an nsIntRect or an nsIntRegion from filter space into the space
+ * that is relative to a filtered frame's origin (i.e. the top-left corner
+ * of its border box) in app units, rounding out.
+ */
+ nsRect FilterSpaceToFrameSpace(const nsIntRect& aRect) const;
+ nsRegion FilterSpaceToFrameSpace(const nsIntRegion& aRegion) const;
+
+ /**
+ * Returns the transform from frame space to the coordinate space that
+ * GetCanvasTM transforms to. "Frame space" is the origin of a frame, aka the
+ * top-left corner of its border box, aka the top left corner of its mRect.
+ */
+ gfxMatrix GetUserSpaceToFrameSpaceInCSSPxTransform() const;
+
+ bool ComputeTargetBBoxInFilterSpace();
+
+ /**
+ * The frame for the element that is currently being filtered.
+ */
+ nsIFrame* mTargetFrame;
+
+ /**
+ * The filtered element.
+ */
+ nsIContent* mTargetContent;
+
+ /**
+ * The user space metrics of the filtered frame.
+ */
+ const UserSpaceMetrics& mMetrics;
+
+ const SVGFilterPaintCallback& mPaintCallback;
+
+ /**
+ * The SVG bbox of the element that is being filtered, in user space.
+ */
+ gfxRect mTargetBBox;
+
+ /**
+ * The SVG bbox of the element that is being filtered, in filter space.
+ */
+ nsIntRect mTargetBBoxInFilterSpace;
+
+ /**
+ * Transform rects between filter space and frame space in CSS pixels.
+ */
+ gfxMatrix mFilterSpaceToFrameSpaceInCSSPxTransform;
+ gfxMatrix mFrameSpaceInCSSPxToFilterSpaceTransform;
+
+ /**
+ * The scale factors between user space and filter space.
+ */
+ gfx::MatrixScalesDouble mUserSpaceToFilterSpaceScale;
+ gfx::MatrixScalesDouble mFilterSpaceToUserSpaceScale;
+
+ /**
+ * Pre-filter paint bounds of the element that is being filtered, in filter
+ * space.
+ */
+ nsIntRect mTargetBounds;
+
+ /**
+ * The dirty area that needs to be repainted, in filter space.
+ */
+ nsIntRegion mPostFilterDirtyRegion;
+
+ /**
+ * The pre-filter area of the filtered element that changed, in filter space.
+ */
+ nsIntRegion mPreFilterDirtyRegion;
+
+ SourceInfo mSourceGraphic;
+ SourceInfo mFillPaint;
+ SourceInfo mStrokePaint;
+
+ /**
+ * The transform to the SVG user space of mTargetFrame.
+ */
+ gfxMatrix mPaintTransform;
+
+ nsTArray<RefPtr<SourceSurface>> mInputImages;
+ FilterDescription mFilterDescription;
+ bool mInitialized;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_FILTERINSTANCE_H_
diff --git a/layout/svg/ISVGDisplayableFrame.h b/layout/svg/ISVGDisplayableFrame.h
new file mode 100644
index 0000000000..90446561a1
--- /dev/null
+++ b/layout/svg/ISVGDisplayableFrame.h
@@ -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/. */
+
+#ifndef LAYOUT_SVG_ISVGDISPLAYABLEFRAME_H_
+#define LAYOUT_SVG_ISVGDISPLAYABLEFRAME_H_
+
+#include "gfxMatrix.h"
+#include "gfxPoint.h"
+#include "gfxRect.h"
+#include "nsQueryFrame.h"
+#include "nsRect.h"
+#include "mozilla/gfx/MatrixFwd.h"
+
+class gfxContext;
+class nsIFrame;
+
+namespace mozilla {
+class SVGAnimatedLengthList;
+class SVGAnimatedNumberList;
+class SVGBBox;
+class SVGLengthList;
+class SVGNumberList;
+class SVGUserUnitList;
+
+namespace image {
+struct imgDrawingParams;
+} // namespace image
+
+/**
+ * This class is used for elements that can be part of a directly displayable
+ * section of a document. This includes SVGGeometryFrame and SVGGFrame.
+ * (Even though the latter doesn't display anything itself, if it contains
+ * SVGGeometryFrame descendants it is can still be part of a displayable
+ * section of a document) This class is not used for elements that can never
+ * display directly, including SVGGradientFrame and SVGPatternFrame. (The
+ * latter may contain displayable content, but it and its content are never
+ * *directly* displayed in a document. It can only end up being displayed by
+ * means of a reference from other content.)
+ *
+ * Note specifically that SVG frames that inherit SVGContainerFrame do *not*
+ * implement this class (only those that inherit SVGDisplayContainerFrame
+ * do.)
+ */
+class ISVGDisplayableFrame : public nsQueryFrame {
+ public:
+ using imgDrawingParams = image::imgDrawingParams;
+
+ NS_DECL_QUERYFRAME_TARGET(ISVGDisplayableFrame)
+
+ /**
+ * Paint this frame.
+ *
+ * SVG is painted using a combination of display lists (trees of
+ * nsDisplayItem built by BuildDisplayList() implementations) and recursive
+ * PaintSVG calls. SVG frames with the NS_FRAME_IS_NONDISPLAY bit set are
+ * always painted using recursive PaintSVG calls since display list painting
+ * would provide no advantages (they wouldn't be retained for invalidation).
+ * Displayed SVG is normally painted via a display list tree created under
+ * SVGOuterSVGFrame::BuildDisplayList, unless the
+ * svg.display-lists.painting.enabled pref has been set to false by the user
+ * in which case it is done via an SVGOuterSVGFrame::PaintSVG() call that
+ * recurses over the entire SVG frame tree. In future we may use PaintSVG()
+ * calls on SVG container frames to avoid display list construction when it
+ * is expensive and unnecessary (see bug 934411).
+ *
+ * @param aTransform The transform that has to be multiplied onto the
+ * DrawTarget in order for drawing to be in this frame's SVG user space.
+ * Implementations of this method should avoid multiplying aTransform onto
+ * the DrawTarget when possible and instead just pass a transform down to
+ * their children. This is preferable because changing the transform is
+ * very expensive for certain DrawTarget backends so it is best to minimize
+ * the number of transform changes.
+ *
+ * @param aImgParams imagelib parameters that may be used when painting
+ * feImage.
+ *
+ * @param aDirtyRect The area being redrawn, in frame offset pixel
+ * coordinates.
+ */
+ virtual void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect = nullptr) = 0;
+
+ /**
+ * Returns the frame that should handle pointer events at aPoint. aPoint is
+ * expected to be in the SVG user space of the frame on which this method is
+ * called. The frame returned may be the frame on which this method is
+ * called, any of its descendants or else nullptr.
+ */
+ virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) = 0;
+
+ // Called on SVG child frames (except NS_FRAME_IS_NONDISPLAY frames)
+ // to update and then invalidate their cached bounds. This method is not
+ // called until after the SVGOuterSVGFrame has had its initial reflow
+ // (i.e. once the SVG viewport dimensions are known). It should also only
+ // be called by SVGOuterSVGFrame during its reflow.
+ virtual void ReflowSVG() = 0;
+
+ // Flags to pass to NotifySVGChange:
+ //
+ // TRANSFORM_CHANGED:
+ // the current transform matrix for this frame has changed
+ // COORD_CONTEXT_CHANGED:
+ // the dimensions of this frame's coordinate context has changed (percentage
+ // lengths must be reevaluated)
+ // FULL_ZOOM_CHANGED:
+ // the page's zoom level has changed
+ enum SVGChangedFlags {
+ TRANSFORM_CHANGED = 0x01,
+ COORD_CONTEXT_CHANGED = 0x02,
+ FULL_ZOOM_CHANGED = 0x04
+ };
+ /**
+ * This is called on a frame when there has been a change to one of its
+ * ancestors that might affect the frame too. SVGChangedFlags are passed
+ * to indicate what changed.
+ *
+ * Implementations do not need to invalidate, since the caller will
+ * invalidate the entire area of the ancestor that changed. However, they
+ * may need to update their bounds.
+ */
+ virtual void NotifySVGChanged(uint32_t aFlags) = 0;
+
+ /**
+ * Get this frame's contribution to the rect returned by a GetBBox() call
+ * that occurred either on this element, or on one of its ancestors.
+ *
+ * SVG defines an element's bbox to be the element's fill bounds in the
+ * userspace established by that element. By allowing callers to pass in the
+ * transform from the userspace established by this element to the userspace
+ * established by an an ancestor, this method allows callers to obtain this
+ * element's fill bounds in the userspace established by that ancestor
+ * instead. In that case, since we return the bounds in a different userspace
+ * (the ancestor's), the bounds we return are not this element's bbox, but
+ * rather this element's contribution to the bbox of the ancestor.
+ *
+ * @param aToBBoxUserspace The transform from the userspace established by
+ * this element to the userspace established by the ancestor on which
+ * getBBox was called. This will be the identity matrix if we are the
+ * element on which getBBox was called.
+ *
+ * @param aFlags Flags indicating whether, stroke, for example, should be
+ * included in the bbox calculation.
+ */
+ virtual SVGBBox GetBBoxContribution(const gfx::Matrix& aToBBoxUserspace,
+ uint32_t aFlags) = 0;
+
+ // Are we a container frame?
+ virtual bool IsDisplayContainer() = 0;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_ISVGDISPLAYABLEFRAME_H_
diff --git a/layout/svg/ISVGSVGFrame.h b/layout/svg/ISVGSVGFrame.h
new file mode 100644
index 0000000000..ce034b5338
--- /dev/null
+++ b/layout/svg/ISVGSVGFrame.h
@@ -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/. */
+
+#ifndef LAYOUT_SVG_ISVGSVGFRAME_H_
+#define LAYOUT_SVG_ISVGSVGFRAME_H_
+
+#include "nsQueryFrame.h"
+
+namespace mozilla {
+
+class ISVGSVGFrame {
+ public:
+ NS_DECL_QUERYFRAME_TARGET(ISVGSVGFrame)
+
+ /**
+ * Called when non-attribute changes have caused the element's width/height
+ * or its for-children transform to change, and to get the element to notify
+ * its children appropriately. aFlags must be set to
+ * ISVGDisplayableFrame::COORD_CONTEXT_CHANGED and/or
+ * ISVGDisplayableFrame::TRANSFORM_CHANGED.
+ */
+ virtual void NotifyViewportOrTransformChanged(uint32_t aFlags) = 0;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_ISVGSVGFRAME_H_
diff --git a/layout/svg/SVGAFrame.cpp b/layout/svg/SVGAFrame.cpp
new file mode 100644
index 0000000000..cbfdf69e62
--- /dev/null
+++ b/layout/svg/SVGAFrame.cpp
@@ -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/. */
+
+// Keep in (case-insensitive) order:
+#include "gfxMatrix.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGContainerFrame.h"
+#include "mozilla/dom/SVGAElement.h"
+#include "mozilla/dom/MutationEventBinding.h"
+#include "SVGLengthList.h"
+
+nsIFrame* NS_NewSVGAFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGAFrame final : public SVGDisplayContainerFrame {
+ friend nsIFrame* ::NS_NewSVGAFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ protected:
+ explicit SVGAFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : SVGDisplayContainerFrame(aStyle, aPresContext, kClassID) {}
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGAFrame)
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ // nsIFrame:
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGA"_ns, aResult);
+ }
+#endif
+};
+
+} // namespace mozilla
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsIFrame* NS_NewSVGAFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGAFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGAFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods
+#ifdef DEBUG
+void SVGAFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::a),
+ "Trying to construct an SVGAFrame for a "
+ "content element that doesn't support the right interfaces");
+
+ SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsresult SVGAFrame::AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) {
+ if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::transform) {
+ // We don't invalidate for transform changes (the layers code does that).
+ // Also note that SVGTransformableElement::GetAttributeChangeHint will
+ // return nsChangeHint_UpdateOverflow for "transform" attribute changes
+ // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
+ NotifySVGChanged(TRANSFORM_CHANGED);
+ }
+
+ // Currently our SMIL implementation does not modify the DOM attributes. Once
+ // we implement the SVG 2 SMIL behaviour this can be removed
+ // SVGAElement::SetAttr/UnsetAttr's ResetLinkState() call will be sufficient.
+ if (aModType == dom::MutationEvent_Binding::SMIL &&
+ aAttribute == nsGkAtoms::href &&
+ (aNameSpaceID == kNameSpaceID_None ||
+ aNameSpaceID == kNameSpaceID_XLink)) {
+ auto* content = static_cast<dom::SVGAElement*>(GetContent());
+
+ // SMIL may change whether an <a> element is a link, in which case we will
+ // need to update the link state.
+ content->ResetLinkState(true, content->ElementHasHref());
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGClipPathFrame.cpp b/layout/svg/SVGClipPathFrame.cpp
new file mode 100644
index 0000000000..157d4becfb
--- /dev/null
+++ b/layout/svg/SVGClipPathFrame.cpp
@@ -0,0 +1,489 @@
+/* -*- 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:
+#include "SVGClipPathFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "AutoReferenceChainGuard.h"
+#include "ImgDrawResult.h"
+#include "gfxContext.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGGeometryFrame.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/SVGClipPathElement.h"
+#include "mozilla/dom/SVGGeometryElement.h"
+#include "nsGkAtoms.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsIFrame* NS_NewSVGClipPathFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGClipPathFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGClipPathFrame)
+
+void SVGClipPathFrame::ApplyClipPath(gfxContext& aContext,
+ nsIFrame* aClippedFrame,
+ const gfxMatrix& aMatrix) {
+ MOZ_ASSERT(IsTrivial(), "Caller needs to use GetClipMask");
+
+ const DrawTarget* drawTarget = aContext.GetDrawTarget();
+
+ // No need for AutoReferenceChainGuard since simple clip paths by definition
+ // don't reference another clip path.
+
+ // Restore current transform after applying clip path:
+ gfxContextMatrixAutoSaveRestore autoRestore(&aContext);
+
+ RefPtr<Path> clipPath;
+
+ ISVGDisplayableFrame* singleClipPathChild = nullptr;
+ IsTrivial(&singleClipPathChild);
+
+ if (singleClipPathChild) {
+ SVGGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild);
+ if (pathFrame && pathFrame->StyleVisibility()->IsVisible()) {
+ SVGGeometryElement* pathElement =
+ static_cast<SVGGeometryElement*>(pathFrame->GetContent());
+
+ gfxMatrix toChildsUserSpace =
+ SVGUtils::GetTransformMatrixInUserSpace(pathFrame) *
+ (GetClipPathTransform(aClippedFrame) * aMatrix);
+
+ gfxMatrix newMatrix = aContext.CurrentMatrixDouble()
+ .PreMultiply(toChildsUserSpace)
+ .NudgeToIntegers();
+ if (!newMatrix.IsSingular()) {
+ aContext.SetMatrixDouble(newMatrix);
+ FillRule clipRule =
+ SVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule);
+ clipPath = pathElement->GetOrBuildPath(drawTarget, clipRule);
+ }
+ }
+ }
+
+ if (clipPath) {
+ aContext.Clip(clipPath);
+ } else {
+ // The spec says clip away everything if we have no children or the
+ // clipping path otherwise can't be resolved:
+ aContext.Clip(Rect());
+ }
+}
+
+static void ComposeExtraMask(DrawTarget* aTarget, SourceSurface* aExtraMask) {
+ MOZ_ASSERT(aExtraMask);
+
+ Matrix origin = aTarget->GetTransform();
+ aTarget->SetTransform(Matrix());
+ aTarget->MaskSurface(ColorPattern(DeviceColor(0.0, 0.0, 0.0, 1.0)),
+ aExtraMask, Point(0, 0),
+ DrawOptions(1.0, CompositionOp::OP_IN));
+ aTarget->SetTransform(origin);
+}
+
+void SVGClipPathFrame::PaintClipMask(gfxContext& aMaskContext,
+ nsIFrame* aClippedFrame,
+ const gfxMatrix& aMatrix,
+ SourceSurface* aExtraMask) {
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+
+ // A clipPath can reference another clipPath, creating a chain of clipPaths
+ // that must all be applied. We re-enter this method for each clipPath in a
+ // chain, so we need to protect against reference chain related crashes etc.:
+ AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ return; // Break reference chain
+ }
+
+ DrawTarget* maskDT = aMaskContext.GetDrawTarget();
+ MOZ_ASSERT(maskDT->GetFormat() == SurfaceFormat::A8);
+
+ // Paint this clipPath's contents into aMaskDT:
+ // We need to set mMatrixForChildren here so that under the PaintSVG calls
+ // on our children (below) our GetCanvasTM() method will return the correct
+ // transform.
+ mMatrixForChildren = GetClipPathTransform(aClippedFrame) * aMatrix;
+
+ // Check if this clipPath is itself clipped by another clipPath:
+ SVGClipPathFrame* clipPathThatClipsClipPath;
+ // XXX check return value?
+ SVGObserverUtils::GetAndObserveClipPath(this, &clipPathThatClipsClipPath);
+ SVGUtils::MaskUsage maskUsage;
+ SVGUtils::DetermineMaskUsage(this, true, maskUsage);
+
+ if (maskUsage.shouldApplyClipPath) {
+ clipPathThatClipsClipPath->ApplyClipPath(aMaskContext, aClippedFrame,
+ aMatrix);
+ } else if (maskUsage.shouldGenerateClipMaskLayer) {
+ RefPtr<SourceSurface> maskSurface = clipPathThatClipsClipPath->GetClipMask(
+ aMaskContext, aClippedFrame, aMatrix);
+ // We want the mask to be untransformed so use the inverse of the current
+ // transform as the maskTransform to compensate.
+ Matrix maskTransform = aMaskContext.CurrentMatrix();
+ maskTransform.Invert();
+ aMaskContext.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, maskSurface,
+ maskTransform);
+ // The corresponding PopGroupAndBlend call below will mask the
+ // blend using |maskSurface|.
+ }
+
+ // Paint our children into the mask:
+ for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
+ PaintFrameIntoMask(kid, aClippedFrame, aMaskContext);
+ }
+
+ if (maskUsage.shouldGenerateClipMaskLayer) {
+ aMaskContext.PopGroupAndBlend();
+ } else if (maskUsage.shouldApplyClipPath) {
+ aMaskContext.PopClip();
+ }
+
+ if (aExtraMask) {
+ ComposeExtraMask(maskDT, aExtraMask);
+ }
+}
+
+void SVGClipPathFrame::PaintFrameIntoMask(nsIFrame* aFrame,
+ nsIFrame* aClippedFrame,
+ gfxContext& aTarget) {
+ ISVGDisplayableFrame* frame = do_QueryFrame(aFrame);
+ if (!frame) {
+ return;
+ }
+
+ // The CTM of each frame referencing us can be different.
+ frame->NotifySVGChanged(ISVGDisplayableFrame::TRANSFORM_CHANGED);
+
+ // Children of this clipPath may themselves be clipped.
+ SVGClipPathFrame* clipPathThatClipsChild;
+ // XXX check return value?
+ if (SVGObserverUtils::GetAndObserveClipPath(aFrame,
+ &clipPathThatClipsChild) ==
+ SVGObserverUtils::eHasRefsSomeInvalid) {
+ return;
+ }
+
+ SVGUtils::MaskUsage maskUsage;
+ SVGUtils::DetermineMaskUsage(aFrame, true, maskUsage);
+ if (maskUsage.shouldApplyClipPath) {
+ clipPathThatClipsChild->ApplyClipPath(aTarget, aClippedFrame,
+ mMatrixForChildren);
+ } else if (maskUsage.shouldGenerateClipMaskLayer) {
+ RefPtr<SourceSurface> maskSurface = clipPathThatClipsChild->GetClipMask(
+ aTarget, aClippedFrame, mMatrixForChildren);
+
+ // We want the mask to be untransformed so use the inverse of the current
+ // transform as the maskTransform to compensate.
+ Matrix maskTransform = aTarget.CurrentMatrix();
+ maskTransform.Invert();
+ aTarget.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, maskSurface,
+ maskTransform);
+ // The corresponding PopGroupAndBlend call below will mask the
+ // blend using |maskSurface|.
+ }
+
+ gfxMatrix toChildsUserSpace = mMatrixForChildren;
+ nsIFrame* child = do_QueryFrame(frame);
+ nsIContent* childContent = child->GetContent();
+ if (childContent->IsSVGElement()) {
+ toChildsUserSpace =
+ SVGUtils::GetTransformMatrixInUserSpace(child) * mMatrixForChildren;
+ }
+
+ // clipPath does not result in any image rendering, so we just use a dummy
+ // imgDrawingParams instead of requiring our caller to pass one.
+ image::imgDrawingParams imgParams;
+
+ // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and
+ // SVGGeometryFrame::Render checks for that state bit and paints
+ // only the geometry (opaque black) if set.
+ frame->PaintSVG(aTarget, toChildsUserSpace, imgParams);
+
+ if (maskUsage.shouldGenerateClipMaskLayer) {
+ aTarget.PopGroupAndBlend();
+ } else if (maskUsage.shouldApplyClipPath) {
+ aTarget.PopClip();
+ }
+}
+
+already_AddRefed<SourceSurface> SVGClipPathFrame::GetClipMask(
+ gfxContext& aReferenceContext, nsIFrame* aClippedFrame,
+ const gfxMatrix& aMatrix, SourceSurface* aExtraMask) {
+ RefPtr<DrawTarget> maskDT =
+ aReferenceContext.GetDrawTarget()->CreateClippedDrawTarget(
+ Rect(), SurfaceFormat::A8);
+ if (!maskDT) {
+ return nullptr;
+ }
+
+ RefPtr<gfxContext> maskContext =
+ gfxContext::CreatePreservingTransformOrNull(maskDT);
+ if (!maskContext) {
+ gfxCriticalError() << "SVGClipPath context problem " << gfx::hexa(maskDT);
+ return nullptr;
+ }
+
+ PaintClipMask(*maskContext, aClippedFrame, aMatrix, aExtraMask);
+
+ RefPtr<SourceSurface> surface = maskDT->Snapshot();
+ return surface.forget();
+}
+
+bool SVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame,
+ const gfxPoint& aPoint) {
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+
+ // A clipPath can reference another clipPath, creating a chain of clipPaths
+ // that must all be applied. We re-enter this method for each clipPath in a
+ // chain, so we need to protect against reference chain related crashes etc.:
+ AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ return false; // Break reference chain
+ }
+
+ gfxMatrix matrix = GetClipPathTransform(aClippedFrame);
+ if (!matrix.Invert()) {
+ return false;
+ }
+ gfxPoint point = matrix.TransformPoint(aPoint);
+
+ // clipPath elements can themselves be clipped by a different clip path. In
+ // that case the other clip path further clips away the element that is being
+ // clipped by the original clipPath. If this clipPath is being clipped by a
+ // different clip path we need to check if it prevents the original element
+ // from receiving events at aPoint:
+ SVGClipPathFrame* clipPathFrame;
+ // XXX check return value?
+ SVGObserverUtils::GetAndObserveClipPath(this, &clipPathFrame);
+ if (clipPathFrame &&
+ !clipPathFrame->PointIsInsideClipPath(aClippedFrame, aPoint)) {
+ return false;
+ }
+
+ for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
+ ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
+ if (SVGFrame) {
+ gfxPoint pointForChild = point;
+
+ gfxMatrix m = SVGUtils::GetTransformMatrixInUserSpace(kid);
+ if (!m.IsIdentity()) {
+ if (!m.Invert()) {
+ return false;
+ }
+ pointForChild = m.TransformPoint(point);
+ }
+ if (SVGFrame->GetFrameForPoint(pointForChild)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool SVGClipPathFrame::IsTrivial(ISVGDisplayableFrame** aSingleChild) {
+ // If the clip path is clipped then it's non-trivial
+ if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) ==
+ SVGObserverUtils::eHasRefsAllValid) {
+ return false;
+ }
+
+ if (aSingleChild) {
+ *aSingleChild = nullptr;
+ }
+
+ ISVGDisplayableFrame* foundChild = nullptr;
+
+ for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
+ ISVGDisplayableFrame* svgChild = do_QueryFrame(kid);
+ if (svgChild) {
+ // We consider a non-trivial clipPath to be one containing
+ // either more than one svg child and/or a svg container
+ if (foundChild || svgChild->IsDisplayContainer()) {
+ return false;
+ }
+
+ // or where the child is itself clipped
+ if (SVGObserverUtils::GetAndObserveClipPath(kid, nullptr) ==
+ SVGObserverUtils::eHasRefsAllValid) {
+ return false;
+ }
+
+ foundChild = svgChild;
+ }
+ }
+ if (aSingleChild) {
+ *aSingleChild = foundChild;
+ }
+ return true;
+}
+
+bool SVGClipPathFrame::IsValid() {
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+
+ // A clipPath can reference another clipPath, creating a chain of clipPaths
+ // that must all be applied. We re-enter this method for each clipPath in a
+ // chain, so we need to protect against reference chain related crashes etc.:
+ AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ return false; // Break reference chain
+ }
+
+ if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) ==
+ SVGObserverUtils::eHasRefsSomeInvalid) {
+ return false;
+ }
+
+ for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
+ LayoutFrameType kidType = kid->Type();
+
+ if (kidType == LayoutFrameType::SVGUse) {
+ for (nsIFrame* grandKid : kid->PrincipalChildList()) {
+ LayoutFrameType grandKidType = grandKid->Type();
+
+ if (grandKidType != LayoutFrameType::SVGGeometry &&
+ grandKidType != LayoutFrameType::SVGText) {
+ return false;
+ }
+ }
+ continue;
+ }
+
+ if (kidType != LayoutFrameType::SVGGeometry &&
+ kidType != LayoutFrameType::SVGText) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+nsresult SVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::transform) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(this);
+ SVGUtils::NotifyChildrenOfSVGChange(
+ this, ISVGDisplayableFrame::TRANSFORM_CHANGED);
+ }
+ if (aAttribute == nsGkAtoms::clipPathUnits) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(this);
+ }
+ }
+
+ return SVGContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+}
+
+void SVGClipPathFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::clipPath),
+ "Content is not an SVG clipPath!");
+
+ AddStateBits(NS_STATE_SVG_CLIPPATH_CHILD);
+ AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
+ SVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+
+gfxMatrix SVGClipPathFrame::GetCanvasTM() { return mMatrixForChildren; }
+
+gfxMatrix SVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame) {
+ SVGClipPathElement* content = static_cast<SVGClipPathElement*>(GetContent());
+
+ gfxMatrix tm = content->PrependLocalTransformsTo({}, eChildToUserSpace) *
+ SVGUtils::GetTransformMatrixInUserSpace(this);
+
+ SVGAnimatedEnumeration* clipPathUnits =
+ &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS];
+
+ uint32_t flags = SVGUtils::eBBoxIncludeFillGeometry |
+ (aClippedFrame->StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone
+ ? SVGUtils::eIncludeOnlyCurrentFrameForNonSVGElement
+ : 0);
+
+ return SVGUtils::AdjustMatrixForUnits(tm, clipPathUnits, aClippedFrame,
+ flags);
+}
+
+SVGBBox SVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox& aBBox,
+ const gfxMatrix& aMatrix,
+ uint32_t aFlags) {
+ SVGClipPathFrame* clipPathThatClipsClipPath;
+ if (SVGObserverUtils::GetAndObserveClipPath(this,
+ &clipPathThatClipsClipPath) ==
+ SVGObserverUtils::eHasRefsSomeInvalid) {
+ return SVGBBox();
+ }
+
+ nsIContent* node = GetContent()->GetFirstChild();
+ SVGBBox unionBBox, tmpBBox;
+ for (; node; node = node->GetNextSibling()) {
+ SVGElement* svgNode = static_cast<SVGElement*>(node);
+ nsIFrame* frame = svgNode->GetPrimaryFrame();
+ if (frame) {
+ ISVGDisplayableFrame* svg = do_QueryFrame(frame);
+ if (svg) {
+ gfxMatrix matrix =
+ SVGUtils::GetTransformMatrixInUserSpace(frame) * aMatrix;
+ tmpBBox = svg->GetBBoxContribution(gfx::ToMatrix(matrix),
+ SVGUtils::eBBoxIncludeFill);
+ SVGClipPathFrame* clipPathFrame;
+ if (SVGObserverUtils::GetAndObserveClipPath(frame, &clipPathFrame) !=
+ SVGObserverUtils::eHasRefsSomeInvalid &&
+ clipPathFrame) {
+ tmpBBox =
+ clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix, aFlags);
+ }
+ if (!(aFlags & SVGUtils::eDoNotClipToBBoxOfContentInsideClipPath)) {
+ tmpBBox.Intersect(aBBox);
+ }
+ unionBBox.UnionEdges(tmpBBox);
+ }
+ }
+ }
+
+ if (clipPathThatClipsClipPath) {
+ tmpBBox = clipPathThatClipsClipPath->GetBBoxForClipPathFrame(aBBox, aMatrix,
+ aFlags);
+ unionBBox.Intersect(tmpBBox);
+ }
+ return unionBBox;
+}
+
+bool SVGClipPathFrame::IsSVGTransformed(Matrix* aOwnTransforms,
+ Matrix* aFromParentTransforms) const {
+ const auto* e = static_cast<SVGElement const*>(GetContent());
+ Matrix m = ToMatrix(e->PrependLocalTransformsTo({}, eUserSpaceToParent));
+
+ if (m.IsIdentity()) {
+ return false;
+ }
+
+ if (aOwnTransforms) {
+ *aOwnTransforms = m;
+ }
+
+ return true;
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGClipPathFrame.h b/layout/svg/SVGClipPathFrame.h
new file mode 100644
index 0000000000..3ef5b11cca
--- /dev/null
+++ b/layout/svg/SVGClipPathFrame.h
@@ -0,0 +1,170 @@
+/* -*- 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 LAYOUT_SVG_SVGCLIPPATHFRAME_H_
+#define LAYOUT_SVG_SVGCLIPPATHFRAME_H_
+
+#include "gfxMatrix.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/SVGContainerFrame.h"
+
+class gfxContext;
+
+namespace mozilla {
+class ISVGDisplayableFrame;
+class PresShell;
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGClipPathFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGClipPathFrame final : public SVGContainerFrame {
+ friend nsIFrame* ::NS_NewSVGClipPathFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ using Matrix = gfx::Matrix;
+ using SourceSurface = gfx::SourceSurface;
+ using imgDrawingParams = image::imgDrawingParams;
+
+ protected:
+ explicit SVGClipPathFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : SVGContainerFrame(aStyle, aPresContext, kClassID),
+ mIsBeingProcessed(false) {
+ AddStateBits(NS_FRAME_IS_NONDISPLAY);
+ }
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGClipPathFrame)
+
+ // nsIFrame methods:
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override {}
+
+ bool IsSVGTransformed(Matrix* aOwnTransforms,
+ Matrix* aFromParentTransforms) const override;
+
+ // SVGClipPathFrame methods:
+
+ /**
+ * Applies the clipPath by pushing a clip path onto the DrawTarget.
+ *
+ * This method must only be used if IsTrivial() returns true, otherwise use
+ * GetClipMask.
+ *
+ * @param aContext The context that the clip path is to be applied to.
+ * @param aClippedFrame The/an nsIFrame of the element that references this
+ * clipPath that is currently being processed.
+ * @param aMatrix The transform from aClippedFrame's user space to aContext's
+ * current transform.
+ */
+ void ApplyClipPath(gfxContext& aContext, nsIFrame* aClippedFrame,
+ const gfxMatrix& aMatrix);
+
+ /**
+ * Returns an alpha mask surface containing the clipping geometry.
+ *
+ * This method must only be used if IsTrivial() returns false, otherwise use
+ * ApplyClipPath.
+ *
+ * @param aReferenceContext Used to determine the backend for and size of the
+ * returned SourceSurface, the size being limited to the device space clip
+ * extents on the context.
+ * @param aClippedFrame The/an nsIFrame of the element that references this
+ * clipPath that is currently being processed.
+ * @param aMatrix The transform from aClippedFrame's user space to aContext's
+ * current transform.
+ * @param [in, optional] aExtraMask An extra surface that the returned
+ * surface should be masked with.
+ */
+ already_AddRefed<SourceSurface> GetClipMask(
+ gfxContext& aReferenceContext, nsIFrame* aClippedFrame,
+ const gfxMatrix& aMatrix, SourceSurface* aExtraMask = nullptr);
+
+ /**
+ * Paint mask directly onto a given context(aMaskContext).
+ *
+ * @param aMaskContext The target of mask been painting on.
+ * @param aClippedFrame The/an nsIFrame of the element that references this
+ * clipPath that is currently being processed.
+ * @param aMatrix The transform from aClippedFrame's user space to
+ * current transform.
+ * @param [in, optional] aExtraMask An extra surface that the returned
+ * surface should be masked with.
+ */
+ void PaintClipMask(gfxContext& aMaskContext, nsIFrame* aClippedFrame,
+ const gfxMatrix& aMatrix, SourceSurface* aExtraMask);
+
+ /**
+ * aPoint is expected to be in aClippedFrame's SVG user space.
+ */
+ bool PointIsInsideClipPath(nsIFrame* aClippedFrame, const gfxPoint& aPoint);
+
+ // Check if this clipPath is made up of more than one geometry object.
+ // If so, the clipping API in cairo isn't enough and we need to use
+ // mask based clipping.
+ bool IsTrivial(ISVGDisplayableFrame** aSingleChild = nullptr);
+
+ bool IsValid();
+
+ // nsIFrame interface:
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGClipPath"_ns, aResult);
+ }
+#endif
+
+ SVGBBox GetBBoxForClipPathFrame(const SVGBBox& aBBox,
+ const gfxMatrix& aMatrix, uint32_t aFlags);
+
+ /**
+ * If the clipPath element transforms its children due to
+ * clipPathUnits="objectBoundingBox" being set on it and/or due to the
+ * 'transform' attribute being set on it, this function returns the resulting
+ * transform.
+ */
+ gfxMatrix GetClipPathTransform(nsIFrame* aClippedFrame);
+
+ private:
+ // SVGContainerFrame methods:
+ gfxMatrix GetCanvasTM() override;
+
+ already_AddRefed<DrawTarget> CreateClipMask(gfxContext& aReferenceContext,
+ gfx::IntPoint& aOffset);
+
+ void PaintFrameIntoMask(nsIFrame* aFrame, nsIFrame* aClippedFrame,
+ gfxContext& aTarget);
+
+ // Set, during a GetClipMask() call, to the transform that still needs to be
+ // concatenated to the transform of the DrawTarget that was passed to
+ // GetClipMask in order to establish the coordinate space that the clipPath
+ // establishes for its contents (i.e. including applying 'clipPathUnits' and
+ // any 'transform' attribute set on the clipPath) specifically for clipping
+ // the frame that was passed to GetClipMask at that moment in time. This is
+ // set so that if our GetCanvasTM method is called while GetClipMask is
+ // painting its children, the returned matrix will include the transforms
+ // that should be used when creating the mask for the frame passed to
+ // GetClipMask.
+ //
+ // Note: The removal of GetCanvasTM is nearly complete, so our GetCanvasTM
+ // may not even be called soon/any more.
+ gfxMatrix mMatrixForChildren;
+
+ // Flag used to indicate whether a methods that may reenter due to
+ // following a reference to another instance is currently executing.
+ bool mIsBeingProcessed;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGCLIPPATHFRAME_H_
diff --git a/layout/svg/SVGContainerFrame.cpp b/layout/svg/SVGContainerFrame.cpp
new file mode 100644
index 0000000000..3ff429c632
--- /dev/null
+++ b/layout/svg/SVGContainerFrame.cpp
@@ -0,0 +1,427 @@
+/* -*- 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:
+#include "SVGContainerFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "ImgDrawResult.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGTextFrame.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/SVGElement.h"
+#include "nsCSSFrameConstructor.h"
+#include "SVGAnimatedTransformList.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::image;
+
+nsIFrame* NS_NewSVGContainerFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ nsIFrame* frame = new (aPresShell)
+ mozilla::SVGContainerFrame(aStyle, aPresShell->GetPresContext(),
+ mozilla::SVGContainerFrame::kClassID);
+ // If we were called directly, then the frame is for a <defs> or
+ // an unknown element type. In both cases we prevent the content
+ // from displaying directly.
+ frame->AddStateBits(NS_FRAME_IS_NONDISPLAY);
+ return frame;
+}
+
+namespace mozilla {
+
+NS_QUERYFRAME_HEAD(SVGContainerFrame)
+ NS_QUERYFRAME_ENTRY(SVGContainerFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+NS_QUERYFRAME_HEAD(SVGDisplayContainerFrame)
+ NS_QUERYFRAME_ENTRY(SVGDisplayContainerFrame)
+ NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame)
+NS_QUERYFRAME_TAIL_INHERITING(SVGContainerFrame)
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGContainerFrame)
+
+void SVGContainerFrame::AppendFrames(ChildListID aListID,
+ nsFrameList&& aFrameList) {
+ InsertFrames(aListID, mFrames.LastChild(), nullptr, std::move(aFrameList));
+}
+
+void SVGContainerFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) {
+ NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
+ NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
+ "inserting after sibling frame with different parent");
+
+ mFrames.InsertFrames(this, aPrevFrame, std::move(aFrameList));
+}
+
+void SVGContainerFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) {
+ NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list");
+
+ mFrames.DestroyFrame(aOldFrame);
+}
+
+bool SVGContainerFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
+ if (mState & NS_FRAME_IS_NONDISPLAY) {
+ // We don't maintain overflow rects.
+ // XXX It would have be better if the restyle request hadn't even happened.
+ return false;
+ }
+ return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
+}
+
+/**
+ * Traverses a frame tree, marking any SVGTextFrame frames as dirty
+ * and calling InvalidateRenderingObservers() on it.
+ *
+ * The reason that this helper exists is because SVGTextFrame is special.
+ * None of the other SVG frames ever need to be reflowed when they have the
+ * NS_FRAME_IS_NONDISPLAY bit set on them because their PaintSVG methods
+ * (and those of any containers that they can validly be contained within) do
+ * not make use of mRect or overflow rects. "em" lengths, etc., are resolved
+ * as those elements are painted.
+ *
+ * SVGTextFrame is different because its anonymous block and inline frames
+ * need to be reflowed in order to get the correct metrics when things like
+ * inherited font-size of an ancestor changes, or a delayed webfont loads and
+ * applies.
+ *
+ * However, we only need to do this work if we were reflowed with
+ * NS_FRAME_IS_DIRTY, which implies that all descendants are dirty. When
+ * that reflow reaches an NS_FRAME_IS_NONDISPLAY frame it would normally
+ * stop, but this helper looks for any SVGTextFrame descendants of such
+ * frames and marks them NS_FRAME_IS_DIRTY so that the next time that they
+ * are painted their anonymous kid will first get the necessary reflow.
+ */
+/* static */
+void SVGContainerFrame::ReflowSVGNonDisplayText(nsIFrame* aContainer) {
+ if (!aContainer->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
+ return;
+ }
+ MOZ_ASSERT(aContainer->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) ||
+ !aContainer->IsFrameOfType(nsIFrame::eSVG),
+ "it is wasteful to call ReflowSVGNonDisplayText on a container "
+ "frame that is not NS_FRAME_IS_NONDISPLAY or not SVG");
+ for (nsIFrame* kid : aContainer->PrincipalChildList()) {
+ LayoutFrameType type = kid->Type();
+ if (type == LayoutFrameType::SVGText) {
+ static_cast<SVGTextFrame*>(kid)->ReflowSVGNonDisplayText();
+ } else {
+ if (kid->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer) ||
+ type == LayoutFrameType::SVGForeignObject ||
+ !kid->IsFrameOfType(nsIFrame::eSVG)) {
+ ReflowSVGNonDisplayText(kid);
+ }
+ }
+ }
+}
+
+void SVGDisplayContainerFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ if (!IsSVGOuterSVGFrame()) {
+ AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD);
+ }
+ AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
+ SVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+
+void SVGDisplayContainerFrame::BuildDisplayList(
+ nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) {
+ // mContent could be a XUL element so check for an SVG element before casting
+ if (mContent->IsSVGElement() &&
+ !static_cast<const SVGElement*>(GetContent())->HasValidDimensions()) {
+ return;
+ }
+ DisplayOutline(aBuilder, aLists);
+ return BuildDisplayListForNonBlockChildren(aBuilder, aLists);
+}
+
+void SVGDisplayContainerFrame::InsertFrames(
+ ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) {
+ // memorize first old frame after insertion point
+ // XXXbz once again, this would work a lot better if the nsIFrame
+ // methods returned framelist iterators....
+ nsIFrame* nextFrame = aPrevFrame ? aPrevFrame->GetNextSibling()
+ : GetChildList(aListID).FirstChild();
+ nsIFrame* firstNewFrame = aFrameList.FirstChild();
+
+ // Insert the new frames
+ SVGContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
+ std::move(aFrameList));
+
+ // If we are not a non-display SVG frame and we do not have a bounds update
+ // pending, then we need to schedule one for our new children:
+ if (!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN |
+ NS_FRAME_IS_NONDISPLAY)) {
+ for (nsIFrame* kid = firstNewFrame; kid != nextFrame;
+ kid = kid->GetNextSibling()) {
+ ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
+ if (SVGFrame) {
+ MOZ_ASSERT(!kid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
+ "Check for this explicitly in the |if|, then");
+ bool isFirstReflow = kid->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
+ // Remove bits so that ScheduleBoundsUpdate will work:
+ kid->RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ // No need to invalidate the new kid's old bounds, so we just use
+ // SVGUtils::ScheduleBoundsUpdate.
+ SVGUtils::ScheduleReflowSVG(kid);
+ if (isFirstReflow) {
+ // Add back the NS_FRAME_FIRST_REFLOW bit:
+ kid->AddStateBits(NS_FRAME_FIRST_REFLOW);
+ }
+ }
+ }
+ }
+}
+
+void SVGDisplayContainerFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) {
+ SVGObserverUtils::InvalidateRenderingObservers(aOldFrame);
+
+ // SVGContainerFrame::RemoveFrame doesn't call down into
+ // nsContainerFrame::RemoveFrame, so it doesn't call FrameNeedsReflow. We
+ // need to schedule a repaint and schedule an update to our overflow rects.
+ SchedulePaint();
+ PresContext()->RestyleManager()->PostRestyleEvent(
+ mContent->AsElement(), RestyleHint{0}, nsChangeHint_UpdateOverflow);
+
+ SVGContainerFrame::RemoveFrame(aListID, aOldFrame);
+}
+
+bool SVGDisplayContainerFrame::IsSVGTransformed(
+ gfx::Matrix* aOwnTransform, gfx::Matrix* aFromParentTransform) const {
+ bool foundTransform = false;
+
+ // Check if our parent has children-only transforms:
+ nsIFrame* parent = GetParent();
+ if (parent &&
+ parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
+ foundTransform =
+ static_cast<SVGContainerFrame*>(parent)->HasChildrenOnlyTransform(
+ aFromParentTransform);
+ }
+
+ // mContent could be a XUL element so check for an SVG element before casting
+ if (mContent->IsSVGElement()) {
+ SVGElement* content = static_cast<SVGElement*>(GetContent());
+ SVGAnimatedTransformList* transformList =
+ content->GetAnimatedTransformList();
+ if ((transformList && transformList->HasTransform()) ||
+ content->GetAnimateMotionTransform()) {
+ if (aOwnTransform) {
+ *aOwnTransform = gfx::ToMatrix(
+ content->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent));
+ }
+ foundTransform = true;
+ }
+ }
+ return foundTransform;
+}
+
+//----------------------------------------------------------------------
+// ISVGDisplayableFrame methods
+
+void SVGDisplayContainerFrame::PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect) {
+ NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY) ||
+ PresContext()->Document()->IsSVGGlyphsDocument(),
+ "If display lists are enabled, only painting of non-display "
+ "SVG should take this code path");
+
+ if (StyleEffects()->mOpacity == 0.0) {
+ return;
+ }
+
+ gfxMatrix matrix = aTransform;
+ if (GetContent()->IsSVGElement()) { // must check before cast
+ matrix = static_cast<const SVGElement*>(GetContent())
+ ->PrependLocalTransformsTo(matrix, eChildToUserSpace);
+ if (matrix.IsSingular()) {
+ return;
+ }
+ }
+
+ for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
+ gfxMatrix m = matrix;
+ // PaintFrameWithEffects() expects the transform that is passed to it to
+ // include the transform to the passed frame's user space, so add it:
+ const nsIContent* content = kid->GetContent();
+ if (content->IsSVGElement()) { // must check before cast
+ const SVGElement* element = static_cast<const SVGElement*>(content);
+ if (!element->HasValidDimensions()) {
+ continue; // nothing to paint for kid
+ }
+
+ m = SVGUtils::GetTransformMatrixInUserSpace(kid) * m;
+ if (m.IsSingular()) {
+ continue;
+ }
+ }
+ SVGUtils::PaintFrameWithEffects(kid, aContext, m, aImgParams, aDirtyRect);
+ }
+}
+
+nsIFrame* SVGDisplayContainerFrame::GetFrameForPoint(const gfxPoint& aPoint) {
+ NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "If display lists are enabled, only hit-testing of a "
+ "clipPath's contents should take this code path");
+ return SVGUtils::HitTestChildren(this, aPoint);
+}
+
+void SVGDisplayContainerFrame::ReflowSVG() {
+ MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this),
+ "This call is probably a wasteful mistake");
+
+ MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
+ "ReflowSVG mechanism not designed for this");
+
+ MOZ_ASSERT(!IsSVGOuterSVGFrame(), "Do not call on outer-<svg>");
+
+ if (!SVGUtils::NeedsReflowSVG(this)) {
+ return;
+ }
+
+ // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame,
+ // then our outer-<svg> has previously had its initial reflow. In that case
+ // we need to make sure that that bit has been removed from ourself _before_
+ // recursing over our children to ensure that they know too. Otherwise, we
+ // need to remove it _after_ recursing over our children so that they know
+ // the initial reflow is currently underway.
+
+ bool isFirstReflow = (mState & NS_FRAME_FIRST_REFLOW);
+
+ bool outerSVGHasHadFirstReflow =
+ !GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
+
+ if (outerSVGHasHadFirstReflow) {
+ RemoveStateBits(NS_FRAME_FIRST_REFLOW); // tell our children
+ }
+
+ OverflowAreas overflowRects;
+
+ for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
+ ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
+ if (SVGFrame) {
+ MOZ_ASSERT(!kid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
+ "Check for this explicitly in the |if|, then");
+ SVGFrame->ReflowSVG();
+
+ // We build up our child frame overflows here instead of using
+ // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same
+ // frame list, and we're iterating over that list now anyway.
+ ConsiderChildOverflow(overflowRects, kid);
+ } else {
+ // Inside a non-display container frame, we might have some
+ // SVGTextFrames. We need to cause those to get reflowed in
+ // case they are the target of a rendering observer.
+ MOZ_ASSERT(
+ kid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) ||
+ !kid->IsFrameOfType(nsIFrame::eSVG),
+ "expected kid to be a NS_FRAME_IS_NONDISPLAY frame or not SVG");
+ if (kid->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
+ SVGContainerFrame* container = do_QueryFrame(kid);
+ if (container && container->GetContent()->IsSVGElement()) {
+ ReflowSVGNonDisplayText(container);
+ }
+ }
+ }
+ }
+
+ // <svg> can create an SVG viewport with an offset due to its
+ // x/y/width/height attributes, and <use> can introduce an offset with an
+ // empty mRect (any width/height is copied to an anonymous <svg> child).
+ // Other than that containers should not set mRect since all other offsets
+ // come from transforms, which are accounted for by nsDisplayTransform.
+ // Note that we rely on |overflow:visible| to allow display list items to be
+ // created for our children.
+ MOZ_ASSERT(mContent->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol) ||
+ (mContent->IsSVGElement(nsGkAtoms::use) &&
+ mRect.Size() == nsSize(0, 0)) ||
+ mRect.IsEqualEdges(nsRect()),
+ "Only inner-<svg>/<use> is expected to have mRect set");
+
+ if (isFirstReflow) {
+ // Make sure we have our filter property (if any) before calling
+ // FinishAndStoreOverflow (subsequent filter changes are handled off
+ // nsChangeHint_UpdateEffects):
+ SVGObserverUtils::UpdateEffects(this);
+ }
+
+ FinishAndStoreOverflow(overflowRects, mRect.Size());
+
+ // Remove state bits after FinishAndStoreOverflow so that it doesn't
+ // invalidate on first reflow:
+ RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void SVGDisplayContainerFrame::NotifySVGChanged(uint32_t aFlags) {
+ MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
+ "Invalidation logic may need adjusting");
+
+ if (aFlags & TRANSFORM_CHANGED) {
+ // make sure our cached transform matrix gets (lazily) updated
+ mCanvasTM = nullptr;
+ }
+
+ SVGUtils::NotifyChildrenOfSVGChange(this, aFlags);
+}
+
+SVGBBox SVGDisplayContainerFrame::GetBBoxContribution(
+ const Matrix& aToBBoxUserspace, uint32_t aFlags) {
+ SVGBBox bboxUnion;
+
+ nsIFrame* kid = mFrames.FirstChild();
+ while (kid) {
+ nsIContent* content = kid->GetContent();
+ ISVGDisplayableFrame* svgKid = do_QueryFrame(kid);
+ // content could be a XUL element so check for an SVG element before casting
+ if (svgKid &&
+ (!content->IsSVGElement() ||
+ static_cast<const SVGElement*>(content)->HasValidDimensions())) {
+ gfxMatrix transform = gfx::ThebesMatrix(aToBBoxUserspace);
+ if (content->IsSVGElement()) {
+ transform = static_cast<SVGElement*>(content)->PrependLocalTransformsTo(
+ {}, eChildToUserSpace) *
+ SVGUtils::GetTransformMatrixInUserSpace(kid) * transform;
+ }
+ // We need to include zero width/height vertical/horizontal lines, so we
+ // have to use UnionEdges.
+ bboxUnion.UnionEdges(
+ svgKid->GetBBoxContribution(gfx::ToMatrix(transform), aFlags));
+ }
+ kid = kid->GetNextSibling();
+ }
+
+ return bboxUnion;
+}
+
+gfxMatrix SVGDisplayContainerFrame::GetCanvasTM() {
+ if (!mCanvasTM) {
+ NS_ASSERTION(GetParent(), "null parent");
+
+ auto* parent = static_cast<SVGContainerFrame*>(GetParent());
+ auto* content = static_cast<SVGElement*>(GetContent());
+
+ gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM());
+
+ mCanvasTM = MakeUnique<gfxMatrix>(tm);
+ }
+
+ return *mCanvasTM;
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGContainerFrame.h b/layout/svg/SVGContainerFrame.h
new file mode 100644
index 0000000000..a78a7ceb3f
--- /dev/null
+++ b/layout/svg/SVGContainerFrame.h
@@ -0,0 +1,163 @@
+/* -*- 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 LAYOUT_SVG_SVGCONTAINERFRAME_H_
+#define LAYOUT_SVG_SVGCONTAINERFRAME_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ISVGDisplayableFrame.h"
+#include "mozilla/UniquePtr.h"
+#include "nsContainerFrame.h"
+#include "nsIFrame.h"
+#include "nsQueryFrame.h"
+#include "nsRect.h"
+
+class gfxContext;
+class nsFrameList;
+class nsIContent;
+
+struct nsRect;
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGContainerFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+/**
+ * Base class for SVG container frames. Frame sub-classes that do not
+ * display their contents directly (such as the frames for <marker> or
+ * <pattern>) just inherit this class. Frame sub-classes that do or can
+ * display their contents directly (such as the frames for inner-<svg> or
+ * <g>) inherit our nsDisplayContainerFrame sub-class.
+ *
+ * *** WARNING ***
+ *
+ * Do *not* blindly cast to SVG element types in this class's methods (see the
+ * warning comment for SVGDisplayContainerFrame below).
+ */
+class SVGContainerFrame : public nsContainerFrame {
+ friend nsIFrame* ::NS_NewSVGContainerFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ protected:
+ SVGContainerFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ ClassID aID)
+ : nsContainerFrame(aStyle, aPresContext, aID) {
+ AddStateBits(NS_FRAME_SVG_LAYOUT);
+ }
+
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(SVGContainerFrame)
+
+ // Returns the transform to our gfxContext (to device pixels, not CSS px)
+ virtual gfxMatrix GetCanvasTM() { return gfxMatrix(); }
+
+ /**
+ * Returns true if the frame's content has a transform that applies only to
+ * its children, and not to the frame itself. For example, an implicit
+ * transform introduced by a 'viewBox' attribute, or an explicit transform
+ * due to a root-<svg> having its currentScale/currentTransform properties
+ * set. If aTransform is non-null, then it will be set to the transform.
+ */
+ virtual bool HasChildrenOnlyTransform(Matrix* aTransform) const {
+ return false;
+ }
+
+ // nsIFrame:
+ void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override;
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) override;
+ void RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) override;
+
+ bool IsFrameOfType(uint32_t aFlags) const override {
+ if (aFlags & eSupportsContainLayoutAndPaint) {
+ return false;
+ }
+
+ return nsContainerFrame::IsFrameOfType(
+ aFlags & ~(nsIFrame::eSVG | nsIFrame::eSVGContainer));
+ }
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override {}
+
+ bool ComputeCustomOverflow(mozilla::OverflowAreas& aOverflowAreas) override;
+
+ protected:
+ /**
+ * Traverses a frame tree, marking any SVGTextFrame frames as dirty
+ * and calling InvalidateRenderingObservers() on it.
+ */
+ static void ReflowSVGNonDisplayText(nsIFrame* aContainer);
+};
+
+/**
+ * Frame class or base-class for SVG containers that can or do display their
+ * contents directly.
+ *
+ * *** WARNING ***
+ *
+ * This class's methods can *not* assume that mContent points to an instance of
+ * an SVG element class since this class is inherited by
+ * SVGGenericContainerFrame which is used for unrecognized elements in the
+ * SVG namespace. Do *not* blindly cast to SVG element types.
+ */
+class SVGDisplayContainerFrame : public SVGContainerFrame,
+ public ISVGDisplayableFrame {
+ protected:
+ SVGDisplayContainerFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ nsIFrame::ClassID aID)
+ : SVGContainerFrame(aStyle, aPresContext, aID) {
+ AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
+ }
+
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_QUERYFRAME_TARGET(SVGDisplayContainerFrame)
+ NS_DECL_ABSTRACT_FRAME(SVGDisplayContainerFrame)
+
+ // nsIFrame:
+ void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) override;
+ void RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) override;
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ bool IsSVGTransformed(Matrix* aOwnTransform = nullptr,
+ Matrix* aFromParentTransform = nullptr) const override;
+
+ // ISVGDisplayableFrame interface:
+ void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect = nullptr) override;
+ nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
+ void ReflowSVG() override;
+ void NotifySVGChanged(uint32_t aFlags) override;
+ SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace,
+ uint32_t aFlags) override;
+ bool IsDisplayContainer() override { return true; }
+ gfxMatrix GetCanvasTM() override;
+
+ protected:
+ /**
+ * Cached canvasTM value.
+ */
+ UniquePtr<gfxMatrix> mCanvasTM;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGCONTAINERFRAME_H_
diff --git a/layout/svg/SVGContextPaint.cpp b/layout/svg/SVGContextPaint.cpp
new file mode 100644
index 0000000000..f6579ed7c1
--- /dev/null
+++ b/layout/svg/SVGContextPaint.cpp
@@ -0,0 +1,370 @@
+/* -*- 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 "SVGContextPaint.h"
+
+#include "gfxContext.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/SVGDocument.h"
+#include "mozilla/extensions/WebExtensionPolicy.h"
+#include "mozilla/StaticPrefs_svg.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGUtils.h"
+#include "SVGPaintServerFrame.h"
+
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+namespace mozilla {
+
+using image::imgDrawingParams;
+
+/* static */
+bool SVGContextPaint::IsAllowedForImageFromURI(nsIURI* aURI) {
+ if (StaticPrefs::svg_context_properties_content_enabled()) {
+ return true;
+ }
+
+ // Context paint is pref'ed off for Web content. Ideally we'd have some
+ // easy means to determine whether the frame that has linked to the image
+ // is a frame for a content node that originated from Web content.
+ // Unfortunately different types of anonymous content, about: documents
+ // such as about:reader, etc. that are "our" code that we ship are
+ // sometimes hard to distinguish from real Web content. As a result,
+ // instead of trying to figure out what content is "ours" we instead let
+ // any content provide image context paint, but only if the image is
+ // chrome:// or resource:// do we return true. This should be sufficient
+ // to stop the image context paint feature being useful to (and therefore
+ // used by and relied upon by) Web content. (We don't want Web content to
+ // use this feature because we're not sure that image context paint is a
+ // good mechanism for wider use, or suitable for specification.)
+ //
+ // Because the default favicon used in the browser UI needs context paint, we
+ // also allow it for page-icon:<page-url>. This exposes context paint to
+ // 3rd-party favicons, but only for history and bookmark items. Other places
+ // such as the tab bar don't use the page-icon protocol to load favicons.
+ //
+ // One case that is not covered by chrome:// or resource:// are WebExtensions,
+ // specifically ones that are "ours". WebExtensions are moz-extension://
+ // regardless if the extension is in-tree or not. Since we don't want
+ // extension developers coming to rely on image context paint either, we only
+ // enable context-paint for extensions that are owned by Mozilla
+ // (based on the extension permission "internal:svgContextPropertiesAllowed").
+ //
+ // We also allow this for browser UI icons that are served up from
+ // Mozilla-controlled domains listed in the
+ // svg.context-properties.content.allowed-domains pref.
+ //
+ nsAutoCString scheme;
+ if (NS_SUCCEEDED(aURI->GetScheme(scheme)) &&
+ (scheme.EqualsLiteral("chrome") || scheme.EqualsLiteral("resource") ||
+ scheme.EqualsLiteral("page-icon"))) {
+ return true;
+ }
+ RefPtr<BasePrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(aURI, OriginAttributes());
+
+ RefPtr<extensions::WebExtensionPolicy> addonPolicy = principal->AddonPolicy();
+ if (addonPolicy) {
+ // Only allowed for extensions that have the
+ // internal:svgContextPropertiesAllowed permission (added internally from
+ // to Mozilla-owned extensions, see `isMozillaExtension` function
+ // defined in Extension.jsm for the exact criteria).
+ return addonPolicy->HasPermission(
+ nsGkAtoms::svgContextPropertiesAllowedPermission);
+ }
+
+ bool isInAllowList = false;
+ principal->IsURIInPrefList("svg.context-properties.content.allowed-domains",
+ &isInAllowList);
+ return isInAllowList;
+}
+
+/**
+ * Stores in |aTargetPaint| information on how to reconstruct the current
+ * fill or stroke pattern. Will also set the paint opacity to transparent if
+ * the paint is set to "none".
+ * @param aOuterContextPaint pattern information from the outer text context
+ * @param aTargetPaint where to store the current pattern information
+ * @param aFillOrStroke member pointer to the paint we are setting up
+ */
+static void SetupInheritablePaint(const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix,
+ nsIFrame* aFrame, float& aOpacity,
+ SVGContextPaint* aOuterContextPaint,
+ SVGContextPaintImpl::Paint& aTargetPaint,
+ StyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ nscolor aDefaultFallbackColor,
+ imgDrawingParams& aImgParams) {
+ const nsStyleSVG* style = aFrame->StyleSVG();
+ SVGPaintServerFrame* ps =
+ SVGObserverUtils::GetAndObservePaintServer(aFrame, aFillOrStroke);
+
+ if (ps) {
+ RefPtr<gfxPattern> pattern =
+ ps->GetPaintServerPattern(aFrame, aDrawTarget, aContextMatrix,
+ aFillOrStroke, aOpacity, aImgParams);
+
+ if (pattern) {
+ aTargetPaint.SetPaintServer(aFrame, aContextMatrix, ps);
+ return;
+ }
+ }
+
+ if (aOuterContextPaint) {
+ RefPtr<gfxPattern> pattern;
+ auto tag = SVGContextPaintImpl::Paint::Tag::None;
+ switch ((style->*aFillOrStroke).kind.tag) {
+ case StyleSVGPaintKind::Tag::ContextFill:
+ tag = SVGContextPaintImpl::Paint::Tag::ContextFill;
+ pattern = aOuterContextPaint->GetFillPattern(
+ aDrawTarget, aOpacity, aContextMatrix, aImgParams);
+ break;
+ case StyleSVGPaintKind::Tag::ContextStroke:
+ tag = SVGContextPaintImpl::Paint::Tag::ContextStroke;
+ pattern = aOuterContextPaint->GetStrokePattern(
+ aDrawTarget, aOpacity, aContextMatrix, aImgParams);
+ break;
+ default:;
+ }
+ if (pattern) {
+ aTargetPaint.SetContextPaint(aOuterContextPaint, tag);
+ return;
+ }
+ }
+
+ nscolor color = SVGUtils::GetFallbackOrPaintColor(
+ *aFrame->Style(), aFillOrStroke, aDefaultFallbackColor);
+ aTargetPaint.SetColor(color);
+}
+
+DrawMode SVGContextPaintImpl::Init(const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix,
+ nsIFrame* aFrame,
+ SVGContextPaint* aOuterContextPaint,
+ imgDrawingParams& aImgParams) {
+ DrawMode toDraw = DrawMode(0);
+
+ const nsStyleSVG* style = aFrame->StyleSVG();
+
+ // fill:
+ if (style->mFill.kind.IsNone()) {
+ SetFillOpacity(0.0f);
+ } else {
+ float opacity =
+ SVGUtils::GetOpacity(style->mFillOpacity, aOuterContextPaint);
+
+ SetupInheritablePaint(aDrawTarget, aContextMatrix, aFrame, opacity,
+ aOuterContextPaint, mFillPaint, &nsStyleSVG::mFill,
+ NS_RGB(0, 0, 0), aImgParams);
+
+ SetFillOpacity(opacity);
+
+ toDraw |= DrawMode::GLYPH_FILL;
+ }
+
+ // stroke:
+ if (style->mStroke.kind.IsNone()) {
+ SetStrokeOpacity(0.0f);
+ } else {
+ float opacity =
+ SVGUtils::GetOpacity(style->mStrokeOpacity, aOuterContextPaint);
+
+ SetupInheritablePaint(
+ aDrawTarget, aContextMatrix, aFrame, opacity, aOuterContextPaint,
+ mStrokePaint, &nsStyleSVG::mStroke, NS_RGBA(0, 0, 0, 0), aImgParams);
+
+ SetStrokeOpacity(opacity);
+
+ toDraw |= DrawMode::GLYPH_STROKE;
+ }
+
+ return toDraw;
+}
+
+void SVGContextPaint::InitStrokeGeometry(gfxContext* aContext,
+ float devUnitsPerSVGUnit) {
+ mStrokeWidth = aContext->CurrentLineWidth() / devUnitsPerSVGUnit;
+ aContext->CurrentDash(mDashes, &mDashOffset);
+ for (uint32_t i = 0; i < mDashes.Length(); i++) {
+ mDashes[i] /= devUnitsPerSVGUnit;
+ }
+ mDashOffset /= devUnitsPerSVGUnit;
+}
+
+SVGContextPaint* SVGContextPaint::GetContextPaint(nsIContent* aContent) {
+ dom::Document* ownerDoc = aContent->OwnerDoc();
+ if (!ownerDoc->IsSVGDocument()) {
+ return nullptr;
+ }
+
+ const auto* contextPaint =
+ ownerDoc->AsSVGDocument()->GetCurrentContextPaint();
+ MOZ_ASSERT_IF(contextPaint, ownerDoc->IsBeingUsedAsImage());
+
+ // XXX The SVGContextPaint that SVGDocument keeps around is const. We could
+ // and should keep that constness to the SVGContextPaint that we get here
+ // (SVGImageContext is never changed after it is initialized).
+ //
+ // Unfortunately lazy initialization of SVGContextPaint (which is a member of
+ // SVGImageContext, and also conceptually never changes after construction)
+ // prevents some of SVGContextPaint's conceptually const methods from being
+ // const. Trying to fix SVGContextPaint (perhaps by using |mutable|) is a
+ // bit of a headache so for now we punt on that, don't reapply the constness
+ // to the SVGContextPaint here, and trust that no one will add code that
+ // actually modifies the object.
+ return const_cast<SVGContextPaint*>(contextPaint);
+}
+
+already_AddRefed<gfxPattern> SVGContextPaintImpl::GetFillPattern(
+ const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM,
+ imgDrawingParams& aImgParams) {
+ return mFillPaint.GetPattern(aDrawTarget, aOpacity, &nsStyleSVG::mFill, aCTM,
+ aImgParams);
+}
+
+already_AddRefed<gfxPattern> SVGContextPaintImpl::GetStrokePattern(
+ const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM,
+ imgDrawingParams& aImgParams) {
+ return mStrokePaint.GetPattern(aDrawTarget, aOpacity, &nsStyleSVG::mStroke,
+ aCTM, aImgParams);
+}
+
+already_AddRefed<gfxPattern> SVGContextPaintImpl::Paint::GetPattern(
+ const DrawTarget* aDrawTarget, float aOpacity,
+ StyleSVGPaint nsStyleSVG::*aFillOrStroke, const gfxMatrix& aCTM,
+ imgDrawingParams& aImgParams) {
+ RefPtr<gfxPattern> pattern;
+ if (mPatternCache.Get(aOpacity, getter_AddRefs(pattern))) {
+ // Set the pattern matrix just in case it was messed with by a previous
+ // caller. We should get the same matrix each time a pattern is constructed
+ // so this should be fine.
+ pattern->SetMatrix(aCTM * mPatternMatrix);
+ return pattern.forget();
+ }
+
+ switch (mPaintType) {
+ case Tag::None:
+ pattern = new gfxPattern(DeviceColor());
+ mPatternMatrix = gfxMatrix();
+ break;
+ case Tag::Color: {
+ DeviceColor color = ToDeviceColor(mPaintDefinition.mColor);
+ color.a *= aOpacity;
+ pattern = new gfxPattern(color);
+ mPatternMatrix = gfxMatrix();
+ break;
+ }
+ case Tag::PaintServer:
+ pattern = mPaintDefinition.mPaintServerFrame->GetPaintServerPattern(
+ mFrame, aDrawTarget, mContextMatrix, aFillOrStroke, aOpacity,
+ aImgParams);
+ {
+ // m maps original-user-space to pattern space
+ gfxMatrix m = pattern->GetMatrix();
+ gfxMatrix deviceToOriginalUserSpace = mContextMatrix;
+ if (!deviceToOriginalUserSpace.Invert()) {
+ return nullptr;
+ }
+ // mPatternMatrix maps device space to pattern space via original user
+ // space
+ mPatternMatrix = deviceToOriginalUserSpace * m;
+ }
+ pattern->SetMatrix(aCTM * mPatternMatrix);
+ break;
+ case Tag::ContextFill:
+ pattern = mPaintDefinition.mContextPaint->GetFillPattern(
+ aDrawTarget, aOpacity, aCTM, aImgParams);
+ // Don't cache this. mContextPaint will have cached it anyway. If we
+ // cache it, we'll have to compute mPatternMatrix, which is annoying.
+ return pattern.forget();
+ case Tag::ContextStroke:
+ pattern = mPaintDefinition.mContextPaint->GetStrokePattern(
+ aDrawTarget, aOpacity, aCTM, aImgParams);
+ // Don't cache this. mContextPaint will have cached it anyway. If we
+ // cache it, we'll have to compute mPatternMatrix, which is annoying.
+ return pattern.forget();
+ default:
+ MOZ_ASSERT(false, "invalid paint type");
+ return nullptr;
+ }
+
+ mPatternCache.InsertOrUpdate(aOpacity, RefPtr{pattern});
+ return pattern.forget();
+}
+
+AutoSetRestoreSVGContextPaint::AutoSetRestoreSVGContextPaint(
+ const SVGContextPaint& aContextPaint, dom::SVGDocument& aSVGDocument)
+ : mSVGDocument(aSVGDocument),
+ mOuterContextPaint(aSVGDocument.GetCurrentContextPaint()) {
+ MOZ_ASSERT(aSVGDocument.IsBeingUsedAsImage(),
+ "SVGContextPaint::GetContextPaint assumes this");
+
+ mSVGDocument.SetCurrentContextPaint(&aContextPaint);
+}
+
+AutoSetRestoreSVGContextPaint::~AutoSetRestoreSVGContextPaint() {
+ mSVGDocument.SetCurrentContextPaint(mOuterContextPaint);
+}
+
+// SVGEmbeddingContextPaint
+
+already_AddRefed<gfxPattern> SVGEmbeddingContextPaint::GetFillPattern(
+ const DrawTarget* aDrawTarget, float aFillOpacity, const gfxMatrix& aCTM,
+ imgDrawingParams& aImgParams) {
+ if (!mFill) {
+ return nullptr;
+ }
+ // The gfxPattern that we create below depends on aFillOpacity, and since
+ // different elements in the SVG image may pass in different values for
+ // fill opacities we don't try to cache the gfxPattern that we create.
+ DeviceColor fill = *mFill;
+ fill.a *= aFillOpacity;
+ return do_AddRef(new gfxPattern(fill));
+}
+
+already_AddRefed<gfxPattern> SVGEmbeddingContextPaint::GetStrokePattern(
+ const DrawTarget* aDrawTarget, float aStrokeOpacity, const gfxMatrix& aCTM,
+ imgDrawingParams& aImgParams) {
+ if (!mStroke) {
+ return nullptr;
+ }
+ DeviceColor stroke = *mStroke;
+ stroke.a *= aStrokeOpacity;
+ return do_AddRef(new gfxPattern(stroke));
+}
+
+uint32_t SVGEmbeddingContextPaint::Hash() const {
+ uint32_t hash = 0;
+
+ if (mFill) {
+ hash = HashGeneric(hash, mFill->ToABGR());
+ } else {
+ // Arbitrary number, just to avoid trivial hash collisions between pairs of
+ // instances where one embedding context has fill set to the same value as
+ // another context has stroke set to.
+ hash = 1;
+ }
+
+ if (mStroke) {
+ hash = HashGeneric(hash, mStroke->ToABGR());
+ }
+
+ if (mFillOpacity != 1.0f) {
+ hash = HashGeneric(hash, mFillOpacity);
+ }
+
+ if (mStrokeOpacity != 1.0f) {
+ hash = HashGeneric(hash, mStrokeOpacity);
+ }
+
+ return hash;
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGContextPaint.h b/layout/svg/SVGContextPaint.h
new file mode 100644
index 0000000000..d42066d682
--- /dev/null
+++ b/layout/svg/SVGContextPaint.h
@@ -0,0 +1,287 @@
+/* -*- 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 LAYOUT_SVG_SVGCONTEXTPAINT_H_
+#define LAYOUT_SVG_SVGCONTEXTPAINT_H_
+
+#include "DrawMode.h"
+#include "gfxMatrix.h"
+#include "gfxPattern.h"
+#include "gfxTypes.h"
+#include "gfxUtils.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/gfx/2D.h"
+#include "nsColor.h"
+#include "nsStyleStruct.h"
+#include "nsTArray.h"
+#include "ImgDrawResult.h"
+#include "nsRefPtrHashtable.h"
+
+class gfxContext;
+
+namespace mozilla {
+class SVGPaintServerFrame;
+
+namespace dom {
+class SVGDocument;
+}
+
+/**
+ * This class is used to pass information about a context element through to
+ * SVG painting code in order to resolve the 'context-fill' and related
+ * keywords. See:
+ *
+ * https://www.w3.org/TR/SVG2/painting.html#context-paint
+ *
+ * This feature allows the color in an SVG-in-OpenType glyph to come from the
+ * computed style for the text that is being drawn, for example, or for color
+ * in an SVG embedded by an <img> element to come from the embedding <img>
+ * element.
+ *
+ * This class is reference counted so that it can be shared among many similar
+ * SVGImageContext objects. (SVGImageContext objects are frequently
+ * copy-constructed with small modifications, and we'd like for those copies to
+ * be able to share their context-paint data cheaply.) However, in most cases,
+ * SVGContextPaint instances are stored in a local RefPtr and only last for the
+ * duration of a function call.
+ * XXX Note: SVGImageContext doesn't actually have a SVGContextPaint member yet,
+ * but it will in a later patch in the patch series that added this comment.
+ */
+class SVGContextPaint : public RefCounted<SVGContextPaint> {
+ protected:
+ using DrawTarget = mozilla::gfx::DrawTarget;
+ using Float = mozilla::gfx::Float;
+ using imgDrawingParams = mozilla::image::imgDrawingParams;
+
+ SVGContextPaint() : mDashOffset(0.0f), mStrokeWidth(0.0f) {}
+
+ public:
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(SVGContextPaint)
+
+ virtual ~SVGContextPaint() = default;
+
+ virtual already_AddRefed<gfxPattern> GetFillPattern(
+ const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM,
+ imgDrawingParams& aImgParams) = 0;
+ virtual already_AddRefed<gfxPattern> GetStrokePattern(
+ const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM,
+ imgDrawingParams& aImgParams) = 0;
+ virtual float GetFillOpacity() const = 0;
+ virtual float GetStrokeOpacity() const = 0;
+
+ already_AddRefed<gfxPattern> GetFillPattern(const DrawTarget* aDrawTarget,
+ const gfxMatrix& aCTM,
+ imgDrawingParams& aImgParams) {
+ return GetFillPattern(aDrawTarget, GetFillOpacity(), aCTM, aImgParams);
+ }
+
+ already_AddRefed<gfxPattern> GetStrokePattern(const DrawTarget* aDrawTarget,
+ const gfxMatrix& aCTM,
+ imgDrawingParams& aImgParams) {
+ return GetStrokePattern(aDrawTarget, GetStrokeOpacity(), aCTM, aImgParams);
+ }
+
+ static SVGContextPaint* GetContextPaint(nsIContent* aContent);
+
+ // XXX This gets the geometry params from the gfxContext. We should get that
+ // information from the actual paint context!
+ void InitStrokeGeometry(gfxContext* aContext, float devUnitsPerSVGUnit);
+
+ const FallibleTArray<Float>& GetStrokeDashArray() const { return mDashes; }
+
+ Float GetStrokeDashOffset() const { return mDashOffset; }
+
+ Float GetStrokeWidth() const { return mStrokeWidth; }
+
+ virtual uint32_t Hash() const {
+ MOZ_ASSERT_UNREACHABLE(
+ "Only VectorImage needs to hash, and that should "
+ "only be operating on our SVGEmbeddingContextPaint "
+ "subclass");
+ return 0;
+ }
+
+ /**
+ * Returns true if image context paint is allowed to be used in an image that
+ * has the given URI, else returns false.
+ */
+ static bool IsAllowedForImageFromURI(nsIURI* aURI);
+
+ private:
+ // Member-vars are initialized in InitStrokeGeometry.
+ FallibleTArray<Float> mDashes;
+ MOZ_INIT_OUTSIDE_CTOR Float mDashOffset;
+ MOZ_INIT_OUTSIDE_CTOR Float mStrokeWidth;
+};
+
+/**
+ * RAII class used to temporarily set and remove an SVGContextPaint while a
+ * piece of SVG is being painted. The context paint is set on the SVG's owner
+ * document, as expected by SVGContextPaint::GetContextPaint. Any pre-existing
+ * context paint is restored after this class removes the context paint that it
+ * set.
+ */
+class MOZ_RAII AutoSetRestoreSVGContextPaint {
+ public:
+ AutoSetRestoreSVGContextPaint(const SVGContextPaint& aContextPaint,
+ dom::SVGDocument& aSVGDocument);
+ ~AutoSetRestoreSVGContextPaint();
+
+ private:
+ dom::SVGDocument& mSVGDocument;
+ // The context paint that needs to be restored by our dtor after it removes
+ // aContextPaint:
+ const SVGContextPaint* mOuterContextPaint;
+};
+
+/**
+ * This class should be flattened into SVGContextPaint once we get rid of the
+ * other sub-class (SimpleTextContextPaint).
+ */
+struct SVGContextPaintImpl : public SVGContextPaint {
+ protected:
+ using DrawTarget = mozilla::gfx::DrawTarget;
+
+ public:
+ DrawMode Init(const DrawTarget* aDrawTarget, const gfxMatrix& aContextMatrix,
+ nsIFrame* aFrame, SVGContextPaint* aOuterContextPaint,
+ imgDrawingParams& aImgParams);
+
+ already_AddRefed<gfxPattern> GetFillPattern(
+ const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM,
+ imgDrawingParams& aImgParams) override;
+ already_AddRefed<gfxPattern> GetStrokePattern(
+ const DrawTarget* aDrawTarget, float aOpacity, const gfxMatrix& aCTM,
+ imgDrawingParams& aImgParams) override;
+
+ void SetFillOpacity(float aOpacity) { mFillOpacity = aOpacity; }
+ float GetFillOpacity() const override { return mFillOpacity; }
+
+ void SetStrokeOpacity(float aOpacity) { mStrokeOpacity = aOpacity; }
+ float GetStrokeOpacity() const override { return mStrokeOpacity; }
+
+ struct Paint {
+ enum class Tag : uint8_t {
+ None,
+ Color,
+ PaintServer,
+ ContextFill,
+ ContextStroke,
+ };
+
+ Paint() : mPaintDefinition{}, mPaintType(Tag::None) {}
+
+ void SetPaintServer(nsIFrame* aFrame, const gfxMatrix& aContextMatrix,
+ SVGPaintServerFrame* aPaintServerFrame) {
+ mPaintType = Tag::PaintServer;
+ mPaintDefinition.mPaintServerFrame = aPaintServerFrame;
+ mFrame = aFrame;
+ mContextMatrix = aContextMatrix;
+ }
+
+ void SetColor(const nscolor& aColor) {
+ mPaintType = Tag::Color;
+ mPaintDefinition.mColor = aColor;
+ }
+
+ void SetContextPaint(SVGContextPaint* aContextPaint, Tag aTag) {
+ MOZ_ASSERT(aTag == Tag::ContextFill || aTag == Tag::ContextStroke);
+ mPaintType = aTag;
+ mPaintDefinition.mContextPaint = aContextPaint;
+ }
+
+ union {
+ SVGPaintServerFrame* mPaintServerFrame;
+ SVGContextPaint* mContextPaint;
+ nscolor mColor;
+ } mPaintDefinition;
+
+ // Initialized (if needed) in SetPaintServer():
+ MOZ_INIT_OUTSIDE_CTOR nsIFrame* mFrame;
+ // CTM defining the user space for the pattern we will use.
+ gfxMatrix mContextMatrix;
+ Tag mPaintType;
+
+ // Device-space-to-pattern-space
+ gfxMatrix mPatternMatrix;
+ nsRefPtrHashtable<nsFloatHashKey, gfxPattern> mPatternCache;
+
+ already_AddRefed<gfxPattern> GetPattern(
+ const DrawTarget* aDrawTarget, float aOpacity,
+ StyleSVGPaint nsStyleSVG::*aFillOrStroke, const gfxMatrix& aCTM,
+ imgDrawingParams& aImgParams);
+ };
+
+ Paint mFillPaint;
+ Paint mStrokePaint;
+
+ float mFillOpacity;
+ float mStrokeOpacity;
+};
+
+/**
+ * This class is used to pass context paint to an SVG image when an element
+ * references that image (e.g. via HTML <img> or SVG <image>, or by referencing
+ * it from a CSS property such as 'background-image'). In this case we only
+ * support context colors and not paint servers.
+ */
+class SVGEmbeddingContextPaint : public SVGContextPaint {
+ using DeviceColor = gfx::DeviceColor;
+
+ public:
+ SVGEmbeddingContextPaint() : mFillOpacity(1.0f), mStrokeOpacity(1.0f) {}
+
+ bool operator==(const SVGEmbeddingContextPaint& aOther) const {
+ MOZ_ASSERT(GetStrokeWidth() == aOther.GetStrokeWidth() &&
+ GetStrokeDashOffset() == aOther.GetStrokeDashOffset() &&
+ GetStrokeDashArray() == aOther.GetStrokeDashArray(),
+ "We don't currently include these in the context information "
+ "from an embedding element");
+ return mFill == aOther.mFill && mStroke == aOther.mStroke &&
+ mFillOpacity == aOther.mFillOpacity &&
+ mStrokeOpacity == aOther.mStrokeOpacity;
+ }
+
+ void SetFill(nscolor aFill) { mFill.emplace(gfx::ToDeviceColor(aFill)); }
+ const Maybe<DeviceColor>& GetFill() const { return mFill; }
+ void SetStroke(nscolor aStroke) {
+ mStroke.emplace(gfx::ToDeviceColor(aStroke));
+ }
+ const Maybe<DeviceColor>& GetStroke() const { return mStroke; }
+
+ /**
+ * Returns a pattern of type PatternType::COLOR, or else nullptr.
+ */
+ already_AddRefed<gfxPattern> GetFillPattern(
+ const DrawTarget* aDrawTarget, float aFillOpacity, const gfxMatrix& aCTM,
+ imgDrawingParams& aImgParams) override;
+
+ /**
+ * Returns a pattern of type PatternType::COLOR, or else nullptr.
+ */
+ already_AddRefed<gfxPattern> GetStrokePattern(
+ const DrawTarget* aDrawTarget, float aStrokeOpacity,
+ const gfxMatrix& aCTM, imgDrawingParams& aImgParams) override;
+
+ void SetFillOpacity(float aOpacity) { mFillOpacity = aOpacity; }
+ float GetFillOpacity() const override { return mFillOpacity; };
+
+ void SetStrokeOpacity(float aOpacity) { mStrokeOpacity = aOpacity; }
+ float GetStrokeOpacity() const override { return mStrokeOpacity; };
+
+ uint32_t Hash() const override;
+
+ private:
+ Maybe<DeviceColor> mFill;
+ Maybe<DeviceColor> mStroke;
+ float mFillOpacity;
+ float mStrokeOpacity;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGCONTEXTPAINT_H_
diff --git a/layout/svg/SVGFEContainerFrame.cpp b/layout/svg/SVGFEContainerFrame.cpp
new file mode 100644
index 0000000000..0df17c7814
--- /dev/null
+++ b/layout/svg/SVGFEContainerFrame.cpp
@@ -0,0 +1,108 @@
+/* -*- 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/. */
+
+// Keep in (case-insensitive) order:
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/dom/SVGFilters.h"
+#include "nsContainerFrame.h"
+#include "nsGkAtoms.h"
+#include "nsIFrame.h"
+#include "nsLiteralString.h"
+
+using namespace mozilla::dom;
+
+nsIFrame* NS_NewSVGFEContainerFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+/*
+ * This frame is used by filter primitive elements that
+ * have special child elements that provide parameters.
+ */
+class SVGFEContainerFrame final : public nsContainerFrame {
+ friend nsIFrame* ::NS_NewSVGFEContainerFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ protected:
+ explicit SVGFEContainerFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsContainerFrame(aStyle, aPresContext, kClassID) {
+ AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY);
+ }
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGFEContainerFrame)
+
+ bool IsFrameOfType(uint32_t aFlags) const override {
+ if (aFlags & eSupportsContainLayoutAndPaint) {
+ return false;
+ }
+
+ return nsContainerFrame::IsFrameOfType(
+ aFlags & ~(nsIFrame::eSVG | nsIFrame::eSVGContainer));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGFEContainer"_ns, aResult);
+ }
+#endif
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ bool ComputeCustomOverflow(OverflowAreas& aOverflowAreas) override {
+ // We don't maintain a ink overflow rect
+ return false;
+ }
+};
+
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGFEContainerFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGFEContainerFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGFEContainerFrame)
+
+#ifdef DEBUG
+void SVGFEContainerFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ nsCOMPtr<SVGFE> filterPrimitive = do_QueryInterface(aContent);
+ NS_ASSERTION(filterPrimitive,
+ "Trying to construct an SVGFEContainerFrame for a "
+ "content element that doesn't support the right interfaces");
+
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsresult SVGFEContainerFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ dom::SVGFE* element = static_cast<dom::SVGFE*>(GetContent());
+ if (element->AttributeAffectsRendering(aNameSpaceID, aAttribute)) {
+ MOZ_ASSERT(
+ GetParent()->IsSVGFilterFrame(),
+ "Observers observe the filter, so that's what we must invalidate");
+ SVGObserverUtils::InvalidateDirectRenderingObservers(GetParent());
+ }
+
+ return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGFEImageFrame.cpp b/layout/svg/SVGFEImageFrame.cpp
new file mode 100644
index 0000000000..ac60a5a791
--- /dev/null
+++ b/layout/svg/SVGFEImageFrame.cpp
@@ -0,0 +1,172 @@
+/* -*- 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/. */
+
+// Keep in (case-insensitive) order:
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/dom/SVGFEImageElement.h"
+#include "mozilla/dom/MutationEventBinding.h"
+#include "nsContainerFrame.h"
+#include "nsIFrame.h"
+#include "nsGkAtoms.h"
+#include "nsLiteralString.h"
+
+using namespace mozilla::dom;
+
+nsIFrame* NS_NewSVGFEImageFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGFEImageFrame final : public nsIFrame {
+ friend nsIFrame* ::NS_NewSVGFEImageFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ protected:
+ explicit SVGFEImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsIFrame(aStyle, aPresContext, kClassID) {
+ AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY);
+
+ // This frame isn't actually displayed, but it contains an image and we want
+ // to use the nsImageLoadingContent machinery for managing images, which
+ // requires visibility tracking, so we enable visibility tracking and
+ // forcibly mark it visible below.
+ EnableVisibilityTracking();
+ }
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGFEImageFrame)
+
+ virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+ virtual void DestroyFrom(nsIFrame* aDestructRoot,
+ PostDestroyData& aPostDestroyData) override;
+
+ bool IsFrameOfType(uint32_t aFlags) const override {
+ if (aFlags & eSupportsContainLayoutAndPaint) {
+ return false;
+ }
+
+ return nsIFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGFEImage"_ns, aResult);
+ }
+#endif
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ void OnVisibilityChange(
+ Visibility aNewVisibility,
+ const Maybe<OnNonvisible>& aNonvisibleAction = Nothing()) override;
+
+ bool ComputeCustomOverflow(OverflowAreas& aOverflowAreas) override {
+ // We don't maintain a ink overflow rect
+ return false;
+ }
+};
+
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGFEImageFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGFEImageFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGFEImageFrame)
+
+/* virtual */
+void SVGFEImageFrame::DestroyFrom(nsIFrame* aDestructRoot,
+ PostDestroyData& aPostDestroyData) {
+ DecApproximateVisibleCount();
+
+ nsCOMPtr<nsIImageLoadingContent> imageLoader =
+ do_QueryInterface(nsIFrame::mContent);
+ if (imageLoader) {
+ imageLoader->FrameDestroyed(this);
+ }
+
+ nsIFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
+}
+
+void SVGFEImageFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::feImage),
+ "Trying to construct an SVGFEImageFrame for a "
+ "content element that doesn't support the right interfaces");
+
+ nsIFrame::Init(aContent, aParent, aPrevInFlow);
+
+ // We assume that feImage's are always visible.
+ // This call must happen before the FrameCreated. This is because the
+ // primary frame pointer on our content node isn't set until after this
+ // function ends, so there is no way for the resulting OnVisibilityChange
+ // notification to get a frame. FrameCreated has a workaround for this in
+ // that it passes our frame around so it can be accessed. OnVisibilityChange
+ // doesn't have that workaround.
+ IncApproximateVisibleCount();
+
+ nsCOMPtr<nsIImageLoadingContent> imageLoader =
+ do_QueryInterface(nsIFrame::mContent);
+ if (imageLoader) {
+ imageLoader->FrameCreated(this);
+ }
+}
+
+nsresult SVGFEImageFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ SVGFEImageElement* element = static_cast<SVGFEImageElement*>(GetContent());
+ if (element->AttributeAffectsRendering(aNameSpaceID, aAttribute)) {
+ MOZ_ASSERT(
+ GetParent()->IsSVGFilterFrame(),
+ "Observers observe the filter, so that's what we must invalidate");
+ SVGObserverUtils::InvalidateDirectRenderingObservers(GetParent());
+ }
+
+ // Currently our SMIL implementation does not modify the DOM attributes. Once
+ // we implement the SVG 2 SMIL behaviour this can be removed
+ // SVGFEImageElement::AfterSetAttr's implementation will be sufficient.
+ if (aModType == MutationEvent_Binding::SMIL &&
+ aAttribute == nsGkAtoms::href &&
+ (aNameSpaceID == kNameSpaceID_XLink ||
+ aNameSpaceID == kNameSpaceID_None)) {
+ bool hrefIsSet =
+ element->mStringAttributes[SVGFEImageElement::HREF].IsExplicitlySet() ||
+ element->mStringAttributes[SVGFEImageElement::XLINK_HREF]
+ .IsExplicitlySet();
+ if (hrefIsSet) {
+ element->LoadSVGImage(true, true);
+ } else {
+ element->CancelImageRequests(true);
+ }
+ }
+
+ return nsIFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+void SVGFEImageFrame::OnVisibilityChange(
+ Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
+ nsCOMPtr<nsIImageLoadingContent> imageLoader =
+ do_QueryInterface(nsIFrame::mContent);
+ if (!imageLoader) {
+ MOZ_ASSERT_UNREACHABLE("Should have an nsIImageLoadingContent");
+ nsIFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+ return;
+ }
+
+ imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+
+ nsIFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGFELeafFrame.cpp b/layout/svg/SVGFELeafFrame.cpp
new file mode 100644
index 0000000000..c49ae429b4
--- /dev/null
+++ b/layout/svg/SVGFELeafFrame.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/. */
+
+// Keep in (case-insensitive) order:
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/dom/SVGFilters.h"
+#include "ComputedStyle.h"
+#include "nsContainerFrame.h"
+#include "nsIFrame.h"
+#include "nsGkAtoms.h"
+
+using namespace mozilla::dom;
+
+nsIFrame* NS_NewSVGFELeafFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+namespace mozilla {
+
+/*
+ * This frame is used by filter primitive elements that don't
+ * have special child elements that provide parameters.
+ */
+class SVGFELeafFrame final : public nsIFrame {
+ friend nsIFrame* ::NS_NewSVGFELeafFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ protected:
+ explicit SVGFELeafFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsIFrame(aStyle, aPresContext, kClassID) {
+ AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY);
+ }
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGFELeafFrame)
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ bool IsFrameOfType(uint32_t aFlags) const override {
+ if (aFlags & eSupportsContainLayoutAndPaint) {
+ return false;
+ }
+
+ return nsIFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGFELeaf"_ns, aResult);
+ }
+#endif
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ bool ComputeCustomOverflow(OverflowAreas& aOverflowAreas) override {
+ // We don't maintain a ink overflow rect
+ return false;
+ }
+};
+
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGFELeafFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGFELeafFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGFELeafFrame)
+
+#ifdef DEBUG
+void SVGFELeafFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ nsCOMPtr<SVGFE> filterPrimitive = do_QueryInterface(aContent);
+ NS_ASSERTION(filterPrimitive,
+ "Trying to construct an SVGFELeafFrame for a "
+ "content element that doesn't support the right interfaces");
+
+ nsIFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsresult SVGFELeafFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ auto* element = static_cast<mozilla::dom::SVGFE*>(GetContent());
+ if (element->AttributeAffectsRendering(aNameSpaceID, aAttribute)) {
+ MOZ_ASSERT(
+ GetParent()->IsSVGFilterFrame(),
+ "Observers observe the filter, so that's what we must invalidate");
+ SVGObserverUtils::InvalidateDirectRenderingObservers(GetParent());
+ }
+
+ return nsIFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGFEUnstyledLeafFrame.cpp b/layout/svg/SVGFEUnstyledLeafFrame.cpp
new file mode 100644
index 0000000000..3d103cb920
--- /dev/null
+++ b/layout/svg/SVGFEUnstyledLeafFrame.cpp
@@ -0,0 +1,88 @@
+/* -*- 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/. */
+
+// Keep in (case-insensitive) order:
+#include "mozilla/dom/SVGFilters.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "nsContainerFrame.h"
+#include "nsIFrame.h"
+#include "nsGkAtoms.h"
+
+nsIFrame* NS_NewSVGFEUnstyledLeafFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGFEUnstyledLeafFrame final : public nsIFrame {
+ friend nsIFrame* ::NS_NewSVGFEUnstyledLeafFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ protected:
+ explicit SVGFEUnstyledLeafFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsIFrame(aStyle, aPresContext, kClassID) {
+ AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY);
+ }
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGFEUnstyledLeafFrame)
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override {}
+
+ bool IsFrameOfType(uint32_t aFlags) const override {
+ if (aFlags & eSupportsContainLayoutAndPaint) {
+ return false;
+ }
+
+ return nsIFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGFEUnstyledLeaf"_ns, aResult);
+ }
+#endif
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ bool ComputeCustomOverflow(OverflowAreas& aOverflowAreas) override {
+ // We don't maintain a ink overflow rect
+ return false;
+ }
+};
+
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGFEUnstyledLeafFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGFEUnstyledLeafFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGFEUnstyledLeafFrame)
+
+nsresult SVGFEUnstyledLeafFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ auto* element =
+ static_cast<mozilla::dom::SVGFEUnstyledElement*>(GetContent());
+ if (element->AttributeAffectsRendering(aNameSpaceID, aAttribute)) {
+ MOZ_ASSERT(
+ GetParent()->GetParent()->IsSVGFilterFrame(),
+ "Observers observe the filter, so that's what we must invalidate");
+ SVGObserverUtils::InvalidateDirectRenderingObservers(
+ GetParent()->GetParent());
+ }
+
+ return nsIFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGFilterFrame.cpp b/layout/svg/SVGFilterFrame.cpp
new file mode 100644
index 0000000000..d134e16e30
--- /dev/null
+++ b/layout/svg/SVGFilterFrame.cpp
@@ -0,0 +1,177 @@
+/* -*- 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:
+#include "SVGFilterFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "AutoReferenceChainGuard.h"
+#include "gfxUtils.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/SVGFilterElement.h"
+#include "nsGkAtoms.h"
+#include "SVGObserverUtils.h"
+#include "SVGElement.h"
+#include "SVGFilterInstance.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsIFrame* NS_NewSVGFilterFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGFilterFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGFilterFrame)
+
+uint16_t SVGFilterFrame::GetEnumValue(uint32_t aIndex, nsIContent* aDefault) {
+ SVGAnimatedEnumeration& thisEnum =
+ static_cast<SVGFilterElement*>(GetContent())->mEnumAttributes[aIndex];
+
+ if (thisEnum.IsExplicitlySet()) {
+ return thisEnum.GetAnimValue();
+ }
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return static_cast<SVGFilterElement*>(aDefault)
+ ->mEnumAttributes[aIndex]
+ .GetAnimValue();
+ }
+
+ SVGFilterFrame* next = GetReferencedFilter();
+
+ return next ? next->GetEnumValue(aIndex, aDefault)
+ : static_cast<SVGFilterElement*>(aDefault)
+ ->mEnumAttributes[aIndex]
+ .GetAnimValue();
+}
+
+const SVGAnimatedLength* SVGFilterFrame::GetLengthValue(uint32_t aIndex,
+ nsIContent* aDefault) {
+ const SVGAnimatedLength* thisLength =
+ &static_cast<SVGFilterElement*>(GetContent())->mLengthAttributes[aIndex];
+
+ if (thisLength->IsExplicitlySet()) {
+ return thisLength;
+ }
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return &static_cast<SVGFilterElement*>(aDefault)->mLengthAttributes[aIndex];
+ }
+
+ SVGFilterFrame* next = GetReferencedFilter();
+
+ return next ? next->GetLengthValue(aIndex, aDefault)
+ : &static_cast<SVGFilterElement*>(aDefault)
+ ->mLengthAttributes[aIndex];
+}
+
+const SVGFilterElement* SVGFilterFrame::GetFilterContent(nsIContent* aDefault) {
+ for (nsIContent* child = mContent->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ RefPtr<SVGFE> primitive;
+ CallQueryInterface(child, (SVGFE**)getter_AddRefs(primitive));
+ if (primitive) {
+ return static_cast<SVGFilterElement*>(GetContent());
+ }
+ }
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return static_cast<SVGFilterElement*>(aDefault);
+ }
+
+ SVGFilterFrame* next = GetReferencedFilter();
+
+ return next ? next->GetFilterContent(aDefault)
+ : static_cast<SVGFilterElement*>(aDefault);
+}
+
+SVGFilterFrame* SVGFilterFrame::GetReferencedFilter() {
+ if (mNoHRefURI) {
+ return nullptr;
+ }
+
+ auto GetHref = [this](nsAString& aHref) {
+ SVGFilterElement* filter = static_cast<SVGFilterElement*>(GetContent());
+ if (filter->mStringAttributes[SVGFilterElement::HREF].IsExplicitlySet()) {
+ filter->mStringAttributes[SVGFilterElement::HREF].GetAnimValue(aHref,
+ filter);
+ } else {
+ filter->mStringAttributes[SVGFilterElement::XLINK_HREF].GetAnimValue(
+ aHref, filter);
+ }
+ this->mNoHRefURI = aHref.IsEmpty();
+ };
+
+ nsIFrame* tframe = SVGObserverUtils::GetAndObserveTemplate(this, GetHref);
+ if (tframe) {
+ LayoutFrameType frameType = tframe->Type();
+ if (frameType == LayoutFrameType::SVGFilter) {
+ return static_cast<SVGFilterFrame*>(tframe);
+ }
+ // We don't call SVGObserverUtils::RemoveTemplateObserver and set
+ // `mNoHRefURI = false` here since we want to be invalidated if the ID
+ // specified by our href starts resolving to a different/valid element.
+ }
+
+ return nullptr;
+}
+
+nsresult SVGFilterFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y ||
+ aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height ||
+ aAttribute == nsGkAtoms::filterUnits ||
+ aAttribute == nsGkAtoms::primitiveUnits)) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(this);
+ } else if ((aNameSpaceID == kNameSpaceID_XLink ||
+ aNameSpaceID == kNameSpaceID_None) &&
+ aAttribute == nsGkAtoms::href) {
+ // Blow away our reference, if any
+ SVGObserverUtils::RemoveTemplateObserver(this);
+ mNoHRefURI = false;
+ // And update whoever references us
+ SVGObserverUtils::InvalidateDirectRenderingObservers(this);
+ }
+ return SVGContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+}
+
+#ifdef DEBUG
+void SVGFilterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::filter),
+ "Content is not an SVG filter");
+
+ SVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+} // namespace mozilla
diff --git a/layout/svg/SVGFilterFrame.h b/layout/svg/SVGFilterFrame.h
new file mode 100644
index 0000000000..d6839c3ff4
--- /dev/null
+++ b/layout/svg/SVGFilterFrame.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 LAYOUT_SVG_SVGFILTERFRAME_H_
+#define LAYOUT_SVG_SVGFILTERFRAME_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/SVGContainerFrame.h"
+#include "nsQueryFrame.h"
+
+class nsAtom;
+class nsIContent;
+class nsIFrame;
+
+struct nsRect;
+
+namespace mozilla {
+class SVGAnimatedLength;
+class SVGFilterInstance;
+class PresShell;
+
+namespace dom {
+class SVGFilterElement;
+} // namespace dom
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGFilterFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGFilterFrame final : public SVGContainerFrame {
+ friend nsIFrame* ::NS_NewSVGFilterFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ protected:
+ explicit SVGFilterFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : SVGContainerFrame(aStyle, aPresContext, kClassID),
+ mLoopFlag(false),
+ mNoHRefURI(false) {
+ AddStateBits(NS_FRAME_IS_NONDISPLAY);
+ }
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGFilterFrame)
+
+ // nsIFrame methods:
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override {}
+
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+#ifdef DEBUG
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ private:
+ friend class SVGFilterInstance;
+
+ /**
+ * Parses this frame's href and - if it references another filter - returns
+ * it. It also makes this frame a rendering observer of the specified ID.
+ */
+ SVGFilterFrame* GetReferencedFilter();
+
+ // Accessors to lookup filter attributes
+ uint16_t GetEnumValue(uint32_t aIndex, nsIContent* aDefault);
+ uint16_t GetEnumValue(uint32_t aIndex) {
+ return GetEnumValue(aIndex, mContent);
+ }
+ const mozilla::SVGAnimatedLength* GetLengthValue(uint32_t aIndex,
+ nsIContent* aDefault);
+ const mozilla::SVGAnimatedLength* GetLengthValue(uint32_t aIndex) {
+ return GetLengthValue(aIndex, mContent);
+ }
+ const mozilla::dom::SVGFilterElement* GetFilterContent(nsIContent* aDefault);
+ const mozilla::dom::SVGFilterElement* GetFilterContent() {
+ return GetFilterContent(mContent);
+ }
+
+ // This flag is used to detect loops in xlink:href processing
+ bool mLoopFlag;
+ bool mNoHRefURI;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGFILTERFRAME_H_
diff --git a/layout/svg/SVGFilterInstance.cpp b/layout/svg/SVGFilterInstance.cpp
new file mode 100644
index 0000000000..34885052a9
--- /dev/null
+++ b/layout/svg/SVGFilterInstance.cpp
@@ -0,0 +1,447 @@
+/* -*- 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:
+#include "SVGFilterInstance.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "mozilla/ISVGDisplayableFrame.h"
+#include "mozilla/SVGContentUtils.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/dom/IDTracker.h"
+#include "mozilla/dom/SVGLengthBinding.h"
+#include "mozilla/dom/SVGUnitTypesBinding.h"
+#include "mozilla/dom/SVGFilterElement.h"
+#include "SVGFilterFrame.h"
+#include "FilterSupport.h"
+#include "gfx2DGlue.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::dom::SVGUnitTypes_Binding;
+using namespace mozilla::gfx;
+
+namespace mozilla {
+
+SVGFilterInstance::SVGFilterInstance(
+ const StyleFilter& aFilter, nsIFrame* aTargetFrame,
+ nsIContent* aTargetContent, const UserSpaceMetrics& aMetrics,
+ const gfxRect& aTargetBBox,
+ const MatrixScalesDouble& aUserSpaceToFilterSpaceScale)
+ : mFilter(aFilter),
+ mTargetContent(aTargetContent),
+ mMetrics(aMetrics),
+ mTargetBBox(aTargetBBox),
+ mUserSpaceToFilterSpaceScale(aUserSpaceToFilterSpaceScale),
+ mSourceAlphaAvailable(false),
+ mInitialized(false) {
+ // Get the filter frame.
+ mFilterFrame = GetFilterFrame(aTargetFrame);
+ if (!mFilterFrame) {
+ return;
+ }
+
+ // Get the filter element.
+ mFilterElement = mFilterFrame->GetFilterContent();
+ if (!mFilterElement) {
+ MOZ_ASSERT_UNREACHABLE("filter frame should have a related element");
+ return;
+ }
+
+ mPrimitiveUnits =
+ mFilterFrame->GetEnumValue(SVGFilterElement::PRIMITIVEUNITS);
+
+ if (!ComputeBounds()) {
+ return;
+ }
+
+ mInitialized = true;
+}
+
+bool SVGFilterInstance::ComputeBounds() {
+ // XXX if filterUnits is set (or has defaulted) to objectBoundingBox, we
+ // should send a warning to the error console if the author has used lengths
+ // with units. This is a common mistake and can result in the filter region
+ // being *massive* below (because we ignore the units and interpret the number
+ // as a factor of the bbox width/height). We should also send a warning if the
+ // user uses a number without units (a future SVG spec should really
+ // deprecate that, since it's too confusing for a bare number to be sometimes
+ // interpreted as a fraction of the bounding box and sometimes as user-space
+ // units). So really only percentage values should be used in this case.
+
+ // Set the user space bounds (i.e. the filter region in user space).
+ SVGAnimatedLength XYWH[4];
+ static_assert(sizeof(mFilterElement->mLengthAttributes) == sizeof(XYWH),
+ "XYWH size incorrect");
+ memcpy(XYWH, mFilterElement->mLengthAttributes,
+ sizeof(mFilterElement->mLengthAttributes));
+ XYWH[0] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_X);
+ XYWH[1] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_Y);
+ XYWH[2] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_WIDTH);
+ XYWH[3] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_HEIGHT);
+ uint16_t filterUnits =
+ mFilterFrame->GetEnumValue(SVGFilterElement::FILTERUNITS);
+ gfxRect userSpaceBounds =
+ SVGUtils::GetRelativeRect(filterUnits, XYWH, mTargetBBox, mMetrics);
+
+ // Transform the user space bounds to filter space, so we
+ // can align them with the pixel boundaries of the offscreen surface.
+ // The offscreen surface has the same scale as filter space.
+ gfxRect filterSpaceBounds = UserSpaceToFilterSpace(userSpaceBounds);
+ filterSpaceBounds.RoundOut();
+ if (filterSpaceBounds.width <= 0 || filterSpaceBounds.height <= 0) {
+ // 0 disables rendering, < 0 is error. dispatch error console warning
+ // or error as appropriate.
+ return false;
+ }
+
+ // Set the filter space bounds.
+ if (!gfxUtils::GfxRectToIntRect(filterSpaceBounds, &mFilterSpaceBounds)) {
+ // The filter region is way too big if there is float -> int overflow.
+ return false;
+ }
+
+ return true;
+}
+
+SVGFilterFrame* SVGFilterInstance::GetFilterFrame(nsIFrame* aTargetFrame) {
+ if (!mFilter.IsUrl()) {
+ // The filter is not an SVG reference filter.
+ return nullptr;
+ }
+
+ // Get the target element to use as a point of reference for looking up the
+ // filter element.
+ if (!mTargetContent) {
+ return nullptr;
+ }
+
+ // aTargetFrame can be null if this filter belongs to a
+ // CanvasRenderingContext2D.
+ nsCOMPtr<nsIURI> url;
+ if (aTargetFrame) {
+ RefPtr<URLAndReferrerInfo> urlExtraReferrer =
+ SVGObserverUtils::GetFilterURI(aTargetFrame, mFilter);
+
+ // urlExtraReferrer might be null when mFilter has an invalid url
+ if (!urlExtraReferrer) {
+ return nullptr;
+ }
+
+ url = urlExtraReferrer->GetURI();
+ } else {
+ url = mFilter.AsUrl().ResolveLocalRef(mTargetContent);
+ }
+
+ if (!url) {
+ return nullptr;
+ }
+
+ // Look up the filter element by URL.
+ IDTracker idTracker;
+ bool watch = false;
+ idTracker.ResetToURIFragmentID(
+ mTargetContent, url, mFilter.AsUrl().ExtraData().ReferrerInfo(), watch);
+ Element* element = idTracker.get();
+ if (!element) {
+ // The URL points to no element.
+ return nullptr;
+ }
+
+ // Get the frame of the filter element.
+ nsIFrame* frame = element->GetPrimaryFrame();
+ if (!frame || !frame->IsSVGFilterFrame()) {
+ // The URL points to an element that's not an SVG filter element, or to an
+ // element that hasn't had its frame constructed yet.
+ return nullptr;
+ }
+
+ return static_cast<SVGFilterFrame*>(frame);
+}
+
+float SVGFilterInstance::GetPrimitiveNumber(uint8_t aCtxType,
+ float aValue) const {
+ SVGAnimatedLength val;
+ val.Init(aCtxType, 0xff, aValue, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER);
+
+ float value;
+ if (mPrimitiveUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ value = SVGUtils::ObjectSpace(mTargetBBox, &val);
+ } else {
+ value = SVGUtils::UserSpace(mMetrics, &val);
+ }
+
+ switch (aCtxType) {
+ case SVGContentUtils::X:
+ return value * static_cast<float>(mUserSpaceToFilterSpaceScale.xScale);
+ case SVGContentUtils::Y:
+ return value * static_cast<float>(mUserSpaceToFilterSpaceScale.yScale);
+ case SVGContentUtils::XY:
+ default:
+ return value * SVGContentUtils::ComputeNormalizedHypotenuse(
+ mUserSpaceToFilterSpaceScale.xScale,
+ mUserSpaceToFilterSpaceScale.yScale);
+ }
+}
+
+Point3D SVGFilterInstance::ConvertLocation(const Point3D& aPoint) const {
+ SVGAnimatedLength val[4];
+ val[0].Init(SVGContentUtils::X, 0xff, aPoint.x,
+ SVGLength_Binding::SVG_LENGTHTYPE_NUMBER);
+ val[1].Init(SVGContentUtils::Y, 0xff, aPoint.y,
+ SVGLength_Binding::SVG_LENGTHTYPE_NUMBER);
+ // Dummy width/height values
+ val[2].Init(SVGContentUtils::X, 0xff, 0,
+ SVGLength_Binding::SVG_LENGTHTYPE_NUMBER);
+ val[3].Init(SVGContentUtils::Y, 0xff, 0,
+ SVGLength_Binding::SVG_LENGTHTYPE_NUMBER);
+
+ gfxRect feArea =
+ SVGUtils::GetRelativeRect(mPrimitiveUnits, val, mTargetBBox, mMetrics);
+ gfxRect r = UserSpaceToFilterSpace(feArea);
+ return Point3D(r.x, r.y, GetPrimitiveNumber(SVGContentUtils::XY, aPoint.z));
+}
+
+gfxRect SVGFilterInstance::UserSpaceToFilterSpace(
+ const gfxRect& aUserSpaceRect) const {
+ gfxRect filterSpaceRect = aUserSpaceRect;
+ filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale);
+ return filterSpaceRect;
+}
+
+IntRect SVGFilterInstance::ComputeFilterPrimitiveSubregion(
+ SVGFE* aFilterElement,
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ const nsTArray<int32_t>& aInputIndices) {
+ SVGFE* fE = aFilterElement;
+
+ IntRect defaultFilterSubregion(0, 0, 0, 0);
+ if (fE->SubregionIsUnionOfRegions()) {
+ for (uint32_t i = 0; i < aInputIndices.Length(); ++i) {
+ int32_t inputIndex = aInputIndices[i];
+ bool isStandardInput =
+ inputIndex < 0 || inputIndex == mSourceGraphicIndex;
+ IntRect inputSubregion =
+ isStandardInput ? mFilterSpaceBounds
+ : aPrimitiveDescrs[inputIndex].PrimitiveSubregion();
+
+ defaultFilterSubregion = defaultFilterSubregion.Union(inputSubregion);
+ }
+ } else {
+ defaultFilterSubregion = mFilterSpaceBounds;
+ }
+
+ gfxRect feArea = SVGUtils::GetRelativeRect(
+ mPrimitiveUnits, &fE->mLengthAttributes[SVGFE::ATTR_X], mTargetBBox,
+ mMetrics);
+ Rect region = ToRect(UserSpaceToFilterSpace(feArea));
+
+ if (!fE->mLengthAttributes[SVGFE::ATTR_X].IsExplicitlySet())
+ region.x = defaultFilterSubregion.X();
+ if (!fE->mLengthAttributes[SVGFE::ATTR_Y].IsExplicitlySet())
+ region.y = defaultFilterSubregion.Y();
+ if (!fE->mLengthAttributes[SVGFE::ATTR_WIDTH].IsExplicitlySet())
+ region.width = defaultFilterSubregion.Width();
+ if (!fE->mLengthAttributes[SVGFE::ATTR_HEIGHT].IsExplicitlySet())
+ region.height = defaultFilterSubregion.Height();
+
+ // We currently require filter primitive subregions to be pixel-aligned.
+ // Following the spec, any pixel partially in the region is included
+ // in the region.
+ region.RoundOut();
+ return RoundedToInt(region);
+}
+
+void SVGFilterInstance::GetInputsAreTainted(
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ const nsTArray<int32_t>& aInputIndices, bool aFilterInputIsTainted,
+ nsTArray<bool>& aOutInputsAreTainted) {
+ for (uint32_t i = 0; i < aInputIndices.Length(); i++) {
+ int32_t inputIndex = aInputIndices[i];
+ if (inputIndex < 0) {
+ aOutInputsAreTainted.AppendElement(aFilterInputIsTainted);
+ } else {
+ aOutInputsAreTainted.AppendElement(
+ aPrimitiveDescrs[inputIndex].IsTainted());
+ }
+ }
+}
+
+static int32_t GetLastResultIndex(
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) {
+ uint32_t numPrimitiveDescrs = aPrimitiveDescrs.Length();
+ return !numPrimitiveDescrs
+ ? FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic
+ : numPrimitiveDescrs - 1;
+}
+
+int32_t SVGFilterInstance::GetOrCreateSourceAlphaIndex(
+ nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) {
+ // If the SourceAlpha index has already been determined or created for this
+ // SVG filter, just return it.
+ if (mSourceAlphaAvailable) {
+ return mSourceAlphaIndex;
+ }
+
+ // If this is the first filter in the chain, we can just use the
+ // kPrimitiveIndexSourceAlpha keyword to refer to the SourceAlpha of the
+ // original image.
+ if (mSourceGraphicIndex < 0) {
+ mSourceAlphaIndex = FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha;
+ mSourceAlphaAvailable = true;
+ return mSourceAlphaIndex;
+ }
+
+ // Otherwise, create a primitive description to turn the previous filter's
+ // output into a SourceAlpha input.
+ FilterPrimitiveDescription descr(AsVariant(ToAlphaAttributes()));
+ descr.SetInputPrimitive(0, mSourceGraphicIndex);
+
+ const FilterPrimitiveDescription& sourcePrimitiveDescr =
+ aPrimitiveDescrs[mSourceGraphicIndex];
+ descr.SetPrimitiveSubregion(sourcePrimitiveDescr.PrimitiveSubregion());
+ descr.SetIsTainted(sourcePrimitiveDescr.IsTainted());
+
+ ColorSpace colorSpace = sourcePrimitiveDescr.OutputColorSpace();
+ descr.SetInputColorSpace(0, colorSpace);
+ descr.SetOutputColorSpace(colorSpace);
+
+ aPrimitiveDescrs.AppendElement(std::move(descr));
+ mSourceAlphaIndex = aPrimitiveDescrs.Length() - 1;
+ mSourceAlphaAvailable = true;
+ return mSourceAlphaIndex;
+}
+
+nsresult SVGFilterInstance::GetSourceIndices(
+ SVGFE* aPrimitiveElement,
+ nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ const nsTHashMap<nsStringHashKey, int32_t>& aImageTable,
+ nsTArray<int32_t>& aSourceIndices) {
+ AutoTArray<SVGStringInfo, 2> sources;
+ aPrimitiveElement->GetSourceImageNames(sources);
+
+ for (uint32_t j = 0; j < sources.Length(); j++) {
+ nsAutoString str;
+ sources[j].mString->GetAnimValue(str, sources[j].mElement);
+
+ int32_t sourceIndex = 0;
+ if (str.EqualsLiteral("SourceGraphic")) {
+ sourceIndex = mSourceGraphicIndex;
+ } else if (str.EqualsLiteral("SourceAlpha")) {
+ sourceIndex = GetOrCreateSourceAlphaIndex(aPrimitiveDescrs);
+ } else if (str.EqualsLiteral("FillPaint")) {
+ sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexFillPaint;
+ } else if (str.EqualsLiteral("StrokePaint")) {
+ sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexStrokePaint;
+ } else if (str.EqualsLiteral("BackgroundImage") ||
+ str.EqualsLiteral("BackgroundAlpha")) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ } else if (str.EqualsLiteral("")) {
+ sourceIndex = GetLastResultIndex(aPrimitiveDescrs);
+ } else {
+ bool inputExists = aImageTable.Get(str, &sourceIndex);
+ if (!inputExists) {
+ sourceIndex = GetLastResultIndex(aPrimitiveDescrs);
+ }
+ }
+
+ aSourceIndices.AppendElement(sourceIndex);
+ }
+ return NS_OK;
+}
+
+nsresult SVGFilterInstance::BuildPrimitives(
+ nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ nsTArray<RefPtr<SourceSurface>>& aInputImages, bool aInputIsTainted) {
+ mSourceGraphicIndex = GetLastResultIndex(aPrimitiveDescrs);
+
+ // Clip previous filter's output to this filter's filter region.
+ if (mSourceGraphicIndex >= 0) {
+ FilterPrimitiveDescription& sourceDescr =
+ aPrimitiveDescrs[mSourceGraphicIndex];
+ sourceDescr.SetPrimitiveSubregion(
+ sourceDescr.PrimitiveSubregion().Intersect(mFilterSpaceBounds));
+ }
+
+ // Get the filter primitive elements.
+ nsTArray<RefPtr<SVGFE>> primitives;
+ for (nsIContent* child = mFilterElement->nsINode::GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ RefPtr<SVGFE> primitive;
+ CallQueryInterface(child, (SVGFE**)getter_AddRefs(primitive));
+ if (primitive) {
+ primitives.AppendElement(primitive);
+ }
+ }
+
+ // Maps source image name to source index.
+ nsTHashMap<nsStringHashKey, int32_t> imageTable(8);
+
+ // The principal that we check principals of any loaded images against.
+ nsCOMPtr<nsIPrincipal> principal = mTargetContent->NodePrincipal();
+
+ for (uint32_t primitiveElementIndex = 0;
+ primitiveElementIndex < primitives.Length(); ++primitiveElementIndex) {
+ SVGFE* filter = primitives[primitiveElementIndex];
+
+ AutoTArray<int32_t, 2> sourceIndices;
+ nsresult rv =
+ GetSourceIndices(filter, aPrimitiveDescrs, imageTable, sourceIndices);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ IntRect primitiveSubregion = ComputeFilterPrimitiveSubregion(
+ filter, aPrimitiveDescrs, sourceIndices);
+
+ nsTArray<bool> sourcesAreTainted;
+ GetInputsAreTainted(aPrimitiveDescrs, sourceIndices, aInputIsTainted,
+ sourcesAreTainted);
+
+ FilterPrimitiveDescription descr = filter->GetPrimitiveDescription(
+ this, primitiveSubregion, sourcesAreTainted, aInputImages);
+
+ descr.SetIsTainted(filter->OutputIsTainted(sourcesAreTainted, principal));
+ descr.SetFilterSpaceBounds(mFilterSpaceBounds);
+ descr.SetPrimitiveSubregion(
+ primitiveSubregion.Intersect(descr.FilterSpaceBounds()));
+
+ for (uint32_t i = 0; i < sourceIndices.Length(); i++) {
+ int32_t inputIndex = sourceIndices[i];
+ descr.SetInputPrimitive(i, inputIndex);
+
+ ColorSpace inputColorSpace =
+ inputIndex >= 0 ? aPrimitiveDescrs[inputIndex].OutputColorSpace()
+ : ColorSpace(ColorSpace::SRGB);
+
+ ColorSpace desiredInputColorSpace =
+ filter->GetInputColorSpace(i, inputColorSpace);
+ descr.SetInputColorSpace(i, desiredInputColorSpace);
+ if (i == 0) {
+ // the output color space is whatever in1 is if there is an in1
+ descr.SetOutputColorSpace(desiredInputColorSpace);
+ }
+ }
+
+ if (sourceIndices.Length() == 0) {
+ descr.SetOutputColorSpace(filter->GetOutputColorSpace());
+ }
+
+ aPrimitiveDescrs.AppendElement(std::move(descr));
+ uint32_t primitiveDescrIndex = aPrimitiveDescrs.Length() - 1;
+
+ nsAutoString str;
+ filter->GetResultImageName().GetAnimValue(str, filter);
+ imageTable.InsertOrUpdate(str, primitiveDescrIndex);
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGFilterInstance.h b/layout/svg/SVGFilterInstance.h
new file mode 100644
index 0000000000..d7cda4ae87
--- /dev/null
+++ b/layout/svg/SVGFilterInstance.h
@@ -0,0 +1,267 @@
+/* -*- 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 LAYOUT_SVG_SVGFILTERINSTANCE_H_
+#define LAYOUT_SVG_SVGFILTERINSTANCE_H_
+
+#include "gfxMatrix.h"
+#include "gfxRect.h"
+#include "SVGAnimatedNumber.h"
+#include "SVGAnimatedNumberPair.h"
+#include "SVGFilters.h"
+#include "mozilla/ServoStyleConsts.h"
+
+namespace mozilla {
+class SVGFilterFrame;
+
+namespace dom {
+class SVGFilterElement;
+} // namespace dom
+
+/**
+ * This class helps FilterInstance build its filter graph by processing a
+ * single SVG reference filter.
+ *
+ * In BuildPrimitives, this class iterates through the referenced <filter>
+ * element's primitive elements, creating a FilterPrimitiveDescription for
+ * each one.
+ *
+ * This class uses several different coordinate spaces, defined as follows:
+ *
+ * "user space"
+ * The filtered SVG element's user space or the filtered HTML element's
+ * CSS pixel space. The origin for an HTML element is the top left corner of
+ * its border box.
+ *
+ * "filter space"
+ * User space scaled to device pixels. Shares the same origin as user space.
+ * This space is the same across chained SVG and CSS filters. To compute the
+ * overall filter space for a chain, we first need to build each filter's
+ * FilterPrimitiveDescriptions in some common space. That space is
+ * filter space.
+ *
+ * To understand the spaces better, let's take an example filter:
+ * <filter id="f">...</filter>
+ *
+ * And apply the filter to a div element:
+ * <div style="filter: url(#f); ...">...</div>
+ *
+ * And let's say there are 2 device pixels for every 1 CSS pixel.
+ *
+ * Finally, let's define an arbitrary point in user space:
+ * "user space point" = (10, 10)
+ *
+ * The point will be inset 10 CSS pixels from both the top and left edges of the
+ * div element's border box.
+ *
+ * Now, let's transform the point from user space to filter space:
+ * "filter space point" = "user space point" * "device pixels per CSS pixel"
+ * "filter space point" = (10, 10) * 2
+ * "filter space point" = (20, 20)
+ */
+class SVGFilterInstance {
+ using Point3D = gfx::Point3D;
+ using IntRect = gfx::IntRect;
+ using SourceSurface = gfx::SourceSurface;
+ using FilterPrimitiveDescription = gfx::FilterPrimitiveDescription;
+ using SVGFE = dom::SVGFE;
+ using UserSpaceMetrics = dom::UserSpaceMetrics;
+
+ public:
+ /**
+ * @param aFilter The SVG filter reference from the style system. This class
+ * stores aFilter by reference, so callers should avoid modifying or
+ * deleting aFilter during the lifetime of SVGFilterInstance.
+ * @param aTargetContent The filtered element.
+ * @param aTargetBBox The SVG bbox to use for the target frame, computed by
+ * the caller. The caller may decide to override the actual SVG bbox.
+ */
+ SVGFilterInstance(
+ const StyleFilter& aFilter, nsIFrame* aTargetFrame,
+ nsIContent* aTargetContent, const UserSpaceMetrics& aMetrics,
+ const gfxRect& aTargetBBox,
+ const gfx::MatrixScalesDouble& aUserSpaceToFilterSpaceScale);
+
+ /**
+ * Returns true if the filter instance was created successfully.
+ */
+ bool IsInitialized() const { return mInitialized; }
+
+ /**
+ * Iterates through the <filter> element's primitive elements, creating a
+ * FilterPrimitiveDescription for each one. Appends the new
+ * FilterPrimitiveDescription(s) to the aPrimitiveDescrs list. Also, appends
+ * new images from feImage filter primitive elements to the aInputImages list.
+ * aInputIsTainted describes whether the input to this filter is tainted, i.e.
+ * whether it contains security-sensitive content. This is needed to propagate
+ * taintedness to the FilterPrimitive that take tainted inputs. Something
+ * being tainted means that it contains security sensitive content. The input
+ * to this filter is the previous filter's output, i.e. the last element in
+ * aPrimitiveDescrs, or the SourceGraphic input if this is the first filter in
+ * the filter chain.
+ */
+ nsresult BuildPrimitives(
+ nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ nsTArray<RefPtr<SourceSurface>>& aInputImages, bool aInputIsTainted);
+
+ float GetPrimitiveNumber(uint8_t aCtxType,
+ const SVGAnimatedNumber* aNumber) const {
+ return GetPrimitiveNumber(aCtxType, aNumber->GetAnimValue());
+ }
+ float GetPrimitiveNumber(uint8_t aCtxType,
+ const SVGAnimatedNumberPair* aNumberPair,
+ SVGAnimatedNumberPair::PairIndex aIndex) const {
+ return GetPrimitiveNumber(aCtxType, aNumberPair->GetAnimValue(aIndex));
+ }
+
+ /**
+ * Converts a userSpaceOnUse/objectBoundingBoxUnits unitless point
+ * into filter space, depending on the value of mPrimitiveUnits. (For
+ * objectBoundingBoxUnits, the bounding box offset is applied to the point.)
+ */
+ Point3D ConvertLocation(const Point3D& aPoint) const;
+
+ /**
+ * Transform a rect between user space and filter space.
+ */
+ gfxRect UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const;
+
+ private:
+ /**
+ * Finds the filter frame associated with this SVG filter.
+ */
+ SVGFilterFrame* GetFilterFrame(nsIFrame* aTargetFrame);
+
+ /**
+ * Computes the filter primitive subregion for the given primitive.
+ */
+ IntRect ComputeFilterPrimitiveSubregion(
+ SVGFE* aFilterElement,
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ const nsTArray<int32_t>& aInputIndices);
+
+ /**
+ * Takes the input indices of a filter primitive and returns for each input
+ * whether the input's output is tainted.
+ */
+ void GetInputsAreTainted(
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ const nsTArray<int32_t>& aInputIndices, bool aFilterInputIsTainted,
+ nsTArray<bool>& aOutInputsAreTainted);
+
+ /**
+ * Scales a numeric filter primitive length in the X, Y or "XY" directions
+ * into a length in filter space (no offset is applied).
+ */
+ float GetPrimitiveNumber(uint8_t aCtxType, float aValue) const;
+
+ /**
+ * Returns the transform from frame space to the coordinate space that
+ * GetCanvasTM transforms to. "Frame space" is the origin of a frame, aka the
+ * top-left corner of its border box, aka the top left corner of its mRect.
+ */
+ gfxMatrix GetUserSpaceToFrameSpaceInCSSPxTransform() const;
+
+ /**
+ * Appends a new FilterPrimitiveDescription to aPrimitiveDescrs that
+ * converts the FilterPrimitiveDescription at mSourceGraphicIndex into
+ * a SourceAlpha input for the next FilterPrimitiveDescription.
+ *
+ * The new FilterPrimitiveDescription zeros out the SourceGraphic's RGB
+ * channels and keeps the alpha channel intact.
+ */
+ int32_t GetOrCreateSourceAlphaIndex(
+ nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs);
+
+ /**
+ * Finds the index in aPrimitiveDescrs of each input to aPrimitiveElement.
+ * For example, if aPrimitiveElement is:
+ * <feGaussianBlur in="another-primitive" .../>
+ * Then, the resulting aSourceIndices will contain the index of the
+ * FilterPrimitiveDescription representing "another-primitive".
+ */
+ nsresult GetSourceIndices(
+ SVGFE* aPrimitiveElement,
+ nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ const nsTHashMap<nsStringHashKey, int32_t>& aImageTable,
+ nsTArray<int32_t>& aSourceIndices);
+
+ /**
+ * Compute the filter region in user space, filter space, and filter
+ * space.
+ */
+ bool ComputeBounds();
+
+ /**
+ * The SVG reference filter originally from the style system.
+ */
+ const StyleFilter& mFilter;
+
+ /**
+ * The filtered element.
+ */
+ nsIContent* mTargetContent;
+
+ /**
+ * The SVG user space metrics that SVG lengths are resolved against.
+ */
+ const UserSpaceMetrics& mMetrics;
+
+ /**
+ * The filter element referenced by mTargetFrame's element.
+ */
+ const dom::SVGFilterElement* mFilterElement;
+
+ /**
+ * The frame for the SVG filter element.
+ */
+ SVGFilterFrame* mFilterFrame;
+
+ /**
+ * The SVG bbox of the element that is being filtered, in user space.
+ */
+ gfxRect mTargetBBox;
+
+ /**
+ * The "filter region" in various spaces.
+ */
+ nsIntRect mFilterSpaceBounds;
+
+ /**
+ * The scale factors between user space and filter space.
+ */
+ gfx::MatrixScalesDouble mUserSpaceToFilterSpaceScale;
+
+ /**
+ * The 'primitiveUnits' attribute value (objectBoundingBox or userSpaceOnUse).
+ */
+ uint16_t mPrimitiveUnits;
+
+ /**
+ * The index of the FilterPrimitiveDescription that this SVG filter should use
+ * as its SourceGraphic, or the SourceGraphic keyword index if this is the
+ * first filter in a chain. Initialized in BuildPrimitives
+ */
+ MOZ_INIT_OUTSIDE_CTOR int32_t mSourceGraphicIndex;
+
+ /**
+ * The index of the FilterPrimitiveDescription that this SVG filter should use
+ * as its SourceAlpha, or the SourceAlpha keyword index if this is the first
+ * filter in a chain. Initialized in BuildPrimitives
+ */
+ MOZ_INIT_OUTSIDE_CTOR int32_t mSourceAlphaIndex;
+
+ /**
+ * SourceAlpha is available if GetOrCreateSourceAlphaIndex has been called.
+ */
+ int32_t mSourceAlphaAvailable;
+
+ bool mInitialized;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGFILTERINSTANCE_H_
diff --git a/layout/svg/SVGForeignObjectFrame.cpp b/layout/svg/SVGForeignObjectFrame.cpp
new file mode 100644
index 0000000000..fd89176991
--- /dev/null
+++ b/layout/svg/SVGForeignObjectFrame.cpp
@@ -0,0 +1,563 @@
+/* -*- 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:
+#include "SVGForeignObjectFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "ImgDrawResult.h"
+#include "gfxContext.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGContainerFrame.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGOuterSVGFrame.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/SVGForeignObjectElement.h"
+#include "nsDisplayList.h"
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+#include "nsLayoutUtils.h"
+#include "nsRegion.h"
+#include "SVGGeometryProperty.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::image;
+namespace SVGT = SVGGeometryProperty::Tags;
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsContainerFrame* NS_NewSVGForeignObjectFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGForeignObjectFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGForeignObjectFrame)
+
+SVGForeignObjectFrame::SVGForeignObjectFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : nsContainerFrame(aStyle, aPresContext, kClassID), mInReflow(false) {
+ AddStateBits(NS_FRAME_REFLOW_ROOT | NS_FRAME_MAY_BE_TRANSFORMED |
+ NS_FRAME_SVG_LAYOUT);
+}
+
+//----------------------------------------------------------------------
+// nsIFrame methods
+
+NS_QUERYFRAME_HEAD(SVGForeignObjectFrame)
+ NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+void SVGForeignObjectFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::foreignObject),
+ "Content is not an SVG foreignObject!");
+
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+ AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD);
+ AddStateBits(NS_FRAME_FONT_INFLATION_CONTAINER |
+ NS_FRAME_FONT_INFLATION_FLOW_ROOT);
+ AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
+ if (!(mState & NS_FRAME_IS_NONDISPLAY)) {
+ SVGUtils::GetOuterSVGFrame(this)->RegisterForeignObject(this);
+ }
+}
+
+void SVGForeignObjectFrame::DestroyFrom(nsIFrame* aDestructRoot,
+ PostDestroyData& aPostDestroyData) {
+ // Only unregister if we registered in the first place:
+ if (!(mState & NS_FRAME_IS_NONDISPLAY)) {
+ SVGUtils::GetOuterSVGFrame(this)->UnregisterForeignObject(this);
+ }
+ nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
+}
+
+nsresult SVGForeignObjectFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::transform) {
+ // We don't invalidate for transform changes (the layers code does that).
+ // Also note that SVGTransformableElement::GetAttributeChangeHint will
+ // return nsChangeHint_UpdateOverflow for "transform" attribute changes
+ // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
+ mCanvasTM = nullptr;
+ } else if (aAttribute == nsGkAtoms::viewBox ||
+ aAttribute == nsGkAtoms::preserveAspectRatio) {
+ nsLayoutUtils::PostRestyleEvent(
+ mContent->AsElement(), RestyleHint{0},
+ nsChangeHint_InvalidateRenderingObservers);
+ }
+ }
+
+ return NS_OK;
+}
+
+void SVGForeignObjectFrame::DidSetComputedStyle(
+ ComputedStyle* aOldComputedStyle) {
+ nsContainerFrame::DidSetComputedStyle(aOldComputedStyle);
+
+ if (aOldComputedStyle) {
+ if (StyleSVGReset()->mX != aOldComputedStyle->StyleSVGReset()->mX ||
+ StyleSVGReset()->mY != aOldComputedStyle->StyleSVGReset()->mY) {
+ // Invalidate cached transform matrix.
+ mCanvasTM = nullptr;
+ SVGUtils::ScheduleReflowSVG(this);
+ }
+ }
+}
+
+void SVGForeignObjectFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+ MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
+ "Should not have been called");
+
+ // Only InvalidateAndScheduleBoundsUpdate marks us with NS_FRAME_IS_DIRTY,
+ // so if that bit is still set we still have a resize pending. If we hit
+ // this assertion, then we should get the presShell to skip reflow roots
+ // that have a dirty parent since a reflow is going to come via the
+ // reflow root's parent anyway.
+ NS_ASSERTION(!HasAnyStateBits(NS_FRAME_IS_DIRTY),
+ "Reflowing while a resize is pending is wasteful");
+
+ // ReflowSVG makes sure mRect is up to date before we're called.
+
+ NS_ASSERTION(!aReflowInput.mParentReflowInput,
+ "should only get reflow from being reflow root");
+ NS_ASSERTION(aReflowInput.ComputedWidth() == GetSize().width &&
+ aReflowInput.ComputedHeight() == GetSize().height,
+ "reflow roots should be reflowed at existing size and "
+ "svg.css should ensure we have no padding/border/margin");
+
+ DoReflow();
+
+ WritingMode wm = aReflowInput.GetWritingMode();
+ LogicalSize finalSize(wm, aReflowInput.ComputedISize(),
+ aReflowInput.ComputedBSize());
+ aDesiredSize.SetSize(wm, finalSize);
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+}
+
+void SVGForeignObjectFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (!static_cast<const SVGElement*>(GetContent())->HasValidDimensions()) {
+ return;
+ }
+ nsDisplayList newList(aBuilder);
+ nsDisplayListSet set(&newList, &newList, &newList, &newList, &newList,
+ &newList);
+ DisplayOutline(aBuilder, set);
+ BuildDisplayListForNonBlockChildren(aBuilder, set);
+ aLists.Content()->AppendNewToTop<nsDisplayForeignObject>(aBuilder, this,
+ &newList);
+}
+
+bool SVGForeignObjectFrame::IsSVGTransformed(
+ Matrix* aOwnTransform, Matrix* aFromParentTransform) const {
+ bool foundTransform = false;
+
+ // Check if our parent has children-only transforms:
+ nsIFrame* parent = GetParent();
+ if (parent &&
+ parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
+ foundTransform =
+ static_cast<SVGContainerFrame*>(parent)->HasChildrenOnlyTransform(
+ aFromParentTransform);
+ }
+
+ SVGElement* content = static_cast<SVGElement*>(GetContent());
+ SVGAnimatedTransformList* transformList = content->GetAnimatedTransformList();
+ if ((transformList && transformList->HasTransform()) ||
+ content->GetAnimateMotionTransform()) {
+ if (aOwnTransform) {
+ *aOwnTransform = gfx::ToMatrix(
+ content->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent));
+ }
+ foundTransform = true;
+ }
+ return foundTransform;
+}
+
+void SVGForeignObjectFrame::PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect) {
+ NS_ASSERTION(
+ !NS_SVGDisplayListPaintingEnabled() || (mState & NS_FRAME_IS_NONDISPLAY),
+ "If display lists are enabled, only painting of non-display "
+ "SVG should take this code path");
+
+ if (IsDisabled()) {
+ return;
+ }
+
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (!kid) {
+ return;
+ }
+
+ if (aTransform.IsSingular()) {
+ NS_WARNING("Can't render foreignObject element!");
+ return;
+ }
+
+ nsRect kidDirtyRect = kid->InkOverflowRect();
+
+ /* Check if we need to draw anything. */
+ if (aDirtyRect) {
+ NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "Display lists handle dirty rect intersection test");
+ // Transform the dirty rect into app units in our userspace.
+ gfxMatrix invmatrix = aTransform;
+ DebugOnly<bool> ok = invmatrix.Invert();
+ NS_ASSERTION(ok, "inverse of non-singular matrix should be non-singular");
+
+ gfxRect transDirtyRect = gfxRect(aDirtyRect->x, aDirtyRect->y,
+ aDirtyRect->width, aDirtyRect->height);
+ transDirtyRect = invmatrix.TransformBounds(transDirtyRect);
+
+ kidDirtyRect.IntersectRect(kidDirtyRect,
+ nsLayoutUtils::RoundGfxRectToAppRect(
+ transDirtyRect, AppUnitsPerCSSPixel()));
+
+ // XXX after bug 614732 is fixed, we will compare mRect with aDirtyRect,
+ // not with kidDirtyRect. I.e.
+ // int32_t appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
+ // mRect.ToOutsidePixels(appUnitsPerDevPx).Intersects(*aDirtyRect)
+ if (kidDirtyRect.IsEmpty()) {
+ return;
+ }
+ }
+
+ aContext.Save();
+
+ if (StyleDisplay()->IsScrollableOverflow()) {
+ float x, y, width, height;
+ SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width,
+ SVGT::Height>(
+ static_cast<SVGElement*>(GetContent()), &x, &y, &width, &height);
+
+ gfxRect clipRect =
+ SVGUtils::GetClipRectForFrame(this, 0.0f, 0.0f, width, height);
+ SVGUtils::SetClipRect(&aContext, aTransform, clipRect);
+ }
+
+ // SVG paints in CSS px, but normally frames paint in dev pixels. Here we
+ // multiply a CSS-px-to-dev-pixel factor onto aTransform so our children
+ // paint correctly.
+ float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(
+ PresContext()->AppUnitsPerDevPixel());
+ gfxMatrix canvasTMForChildren = aTransform;
+ canvasTMForChildren.PreScale(cssPxPerDevPx, cssPxPerDevPx);
+
+ aContext.Multiply(canvasTMForChildren);
+
+ using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
+ PaintFrameFlags flags = PaintFrameFlags::InTransform;
+ if (SVGAutoRenderState::IsPaintingToWindow(aContext.GetDrawTarget())) {
+ flags |= PaintFrameFlags::ToWindow;
+ }
+ if (aImgParams.imageFlags & imgIContainer::FLAG_SYNC_DECODE) {
+ flags |= PaintFrameFlags::SyncDecodeImages;
+ }
+ if (aImgParams.imageFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING) {
+ flags |= PaintFrameFlags::UseHighQualityScaling;
+ }
+ nsLayoutUtils::PaintFrame(&aContext, kid, nsRegion(kidDirtyRect),
+ NS_RGBA(0, 0, 0, 0),
+ nsDisplayListBuilderMode::Painting, flags);
+
+ aContext.Restore();
+}
+
+nsIFrame* SVGForeignObjectFrame::GetFrameForPoint(const gfxPoint& aPoint) {
+ NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "If display lists are enabled, only hit-testing of a "
+ "clipPath's contents should take this code path");
+
+ if (IsDisabled() || HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ return nullptr;
+ }
+
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (!kid) {
+ return nullptr;
+ }
+
+ float x, y, width, height;
+ SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
+ static_cast<SVGElement*>(GetContent()), &x, &y, &width, &height);
+
+ if (!gfxRect(x, y, width, height).Contains(aPoint) ||
+ !SVGUtils::HitTestClip(this, aPoint)) {
+ return nullptr;
+ }
+
+ // Convert the point to app units relative to the top-left corner of the
+ // viewport that's established by the foreignObject element:
+
+ gfxPoint pt = (aPoint + gfxPoint(x, y)) * AppUnitsPerCSSPixel();
+ nsPoint point = nsPoint(NSToIntRound(pt.x), NSToIntRound(pt.y));
+
+ return nsLayoutUtils::GetFrameForPoint(RelativeTo{kid}, point);
+}
+
+void SVGForeignObjectFrame::ReflowSVG() {
+ NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
+ "This call is probably a wasteful mistake");
+
+ MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
+ "ReflowSVG mechanism not designed for this");
+
+ if (!SVGUtils::NeedsReflowSVG(this)) {
+ return;
+ }
+
+ // We update mRect before the DoReflow call so that DoReflow uses the
+ // correct dimensions:
+
+ float x, y, w, h;
+ SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
+ static_cast<SVGElement*>(GetContent()), &x, &y, &w, &h);
+
+ // If mRect's width or height are negative, reflow blows up! We must clamp!
+ if (w < 0.0f) w = 0.0f;
+ if (h < 0.0f) h = 0.0f;
+
+ mRect = nsLayoutUtils::RoundGfxRectToAppRect(gfxRect(x, y, w, h),
+ AppUnitsPerCSSPixel());
+
+ // Fully mark our kid dirty so that it gets resized if necessary
+ // (NS_FRAME_HAS_DIRTY_CHILDREN isn't enough in that case):
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ kid->MarkSubtreeDirty();
+
+ // Make sure to not allow interrupts if we're not being reflown as a root:
+ nsPresContext::InterruptPreventer noInterrupts(PresContext());
+
+ DoReflow();
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ // Make sure we have our filter property (if any) before calling
+ // FinishAndStoreOverflow (subsequent filter changes are handled off
+ // nsChangeHint_UpdateEffects):
+ SVGObserverUtils::UpdateEffects(this);
+ }
+
+ // If we have a filter, we need to invalidate ourselves because filter
+ // output can change even if none of our descendants need repainting.
+ if (StyleEffects()->HasFilters()) {
+ InvalidateFrame();
+ }
+
+ auto* anonKid = PrincipalChildList().FirstChild();
+ nsRect overflow = anonKid->InkOverflowRect();
+
+ OverflowAreas overflowAreas(overflow, overflow);
+ FinishAndStoreOverflow(overflowAreas, mRect.Size());
+
+ // Now unset the various reflow bits:
+ RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void SVGForeignObjectFrame::NotifySVGChanged(uint32_t aFlags) {
+ MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
+ "Invalidation logic may need adjusting");
+
+ bool needNewBounds = false; // i.e. mRect or ink overflow rect
+ bool needReflow = false;
+ bool needNewCanvasTM = false;
+
+ if (aFlags & COORD_CONTEXT_CHANGED) {
+ // Coordinate context changes affect mCanvasTM if we have a
+ // percentage 'x' or 'y'
+ if (StyleSVGReset()->mX.HasPercent() || StyleSVGReset()->mY.HasPercent()) {
+ needNewBounds = true;
+ needNewCanvasTM = true;
+ }
+
+ // Our coordinate context's width/height has changed. If we have a
+ // percentage width/height our dimensions will change so we must reflow.
+ if (StylePosition()->mWidth.HasPercent() ||
+ StylePosition()->mHeight.HasPercent()) {
+ needNewBounds = true;
+ needReflow = true;
+ }
+ }
+
+ if (aFlags & TRANSFORM_CHANGED) {
+ if (mCanvasTM && mCanvasTM->IsSingular()) {
+ needNewBounds = true; // old bounds are bogus
+ }
+ needNewCanvasTM = true;
+ // In an ideal world we would reflow when our CTM changes. This is because
+ // glyph metrics do not necessarily scale uniformly with change in scale
+ // and, as a result, CTM changes may require text to break at different
+ // points. The problem would be how to keep performance acceptable when
+ // e.g. the transform of an ancestor is animated.
+ // We also seem to get some sort of infinite loop post bug 421584 if we
+ // reflow.
+ }
+
+ if (needNewBounds) {
+ // Ancestor changes can't affect how we render from the perspective of
+ // any rendering observers that we may have, so we don't need to
+ // invalidate them. We also don't need to invalidate ourself, since our
+ // changed ancestor will have invalidated its entire area, which includes
+ // our area.
+ SVGUtils::ScheduleReflowSVG(this);
+ }
+
+ // If we're called while the PresShell is handling reflow events then we
+ // must have been called as a result of the NotifyViewportChange() call in
+ // our SVGOuterSVGFrame's Reflow() method. We must not call RequestReflow
+ // at this point (i.e. during reflow) because it could confuse the
+ // PresShell and prevent it from reflowing us properly in future. Besides
+ // that, SVGOuterSVGFrame::DidReflow will take care of reflowing us
+ // synchronously, so there's no need.
+ if (needReflow && !PresShell()->IsReflowLocked()) {
+ RequestReflow(IntrinsicDirty::None);
+ }
+
+ if (needNewCanvasTM) {
+ // Do this after calling InvalidateAndScheduleBoundsUpdate in case we
+ // change the code and it needs to use it.
+ mCanvasTM = nullptr;
+ }
+}
+
+SVGBBox SVGForeignObjectFrame::GetBBoxContribution(
+ const Matrix& aToBBoxUserspace, uint32_t aFlags) {
+ SVGForeignObjectElement* content =
+ static_cast<SVGForeignObjectElement*>(GetContent());
+
+ float x, y, w, h;
+ SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
+ content, &x, &y, &w, &h);
+
+ if (w < 0.0f) w = 0.0f;
+ if (h < 0.0f) h = 0.0f;
+
+ if (aToBBoxUserspace.IsSingular()) {
+ // XXX ReportToConsole
+ return SVGBBox();
+ }
+ return aToBBoxUserspace.TransformBounds(gfx::Rect(0.0, 0.0, w, h));
+}
+
+//----------------------------------------------------------------------
+
+gfxMatrix SVGForeignObjectFrame::GetCanvasTM() {
+ if (!mCanvasTM) {
+ NS_ASSERTION(GetParent(), "null parent");
+
+ auto* parent = static_cast<SVGContainerFrame*>(GetParent());
+ auto* content = static_cast<SVGForeignObjectElement*>(GetContent());
+
+ gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM());
+
+ mCanvasTM = MakeUnique<gfxMatrix>(tm);
+ }
+ return *mCanvasTM;
+}
+
+//----------------------------------------------------------------------
+// Implementation helpers
+
+void SVGForeignObjectFrame::RequestReflow(IntrinsicDirty aType) {
+ if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ // If we haven't had a ReflowSVG() yet, nothing to do.
+ return;
+ }
+
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (!kid) {
+ return;
+ }
+
+ PresShell()->FrameNeedsReflow(kid, aType, NS_FRAME_IS_DIRTY);
+}
+
+void SVGForeignObjectFrame::DoReflow() {
+ MarkInReflow();
+ // Skip reflow if we're zero-sized, unless this is our first reflow.
+ if (IsDisabled() && !HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ return;
+ }
+
+ nsPresContext* presContext = PresContext();
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (!kid) {
+ return;
+ }
+
+ // initiate a synchronous reflow here and now:
+ RefPtr<gfxContext> renderingContext =
+ presContext->PresShell()->CreateReferenceRenderingContext();
+
+ mInReflow = true;
+
+ WritingMode wm = kid->GetWritingMode();
+ ReflowInput reflowInput(presContext, kid, renderingContext,
+ LogicalSize(wm, ISize(wm), NS_UNCONSTRAINEDSIZE));
+ ReflowOutput desiredSize(reflowInput);
+ nsReflowStatus status;
+
+ // We don't use mRect.height above because that tells the child to do
+ // page/column breaking at that height.
+ NS_ASSERTION(
+ reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) &&
+ reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
+ "style system should ensure that :-moz-svg-foreign-content "
+ "does not get styled");
+ NS_ASSERTION(reflowInput.ComputedISize() == ISize(wm),
+ "reflow input made child wrong size");
+ reflowInput.SetComputedBSize(BSize(wm));
+
+ ReflowChild(kid, presContext, desiredSize, reflowInput, 0, 0,
+ ReflowChildFlags::NoMoveFrame, status);
+ NS_ASSERTION(mRect.width == desiredSize.Width() &&
+ mRect.height == desiredSize.Height(),
+ "unexpected size");
+ FinishReflowChild(kid, presContext, desiredSize, &reflowInput, 0, 0,
+ ReflowChildFlags::NoMoveFrame);
+
+ mInReflow = false;
+}
+
+nsRect SVGForeignObjectFrame::GetInvalidRegion() {
+ MOZ_ASSERT(!NS_SVGDisplayListPaintingEnabled(),
+ "Only called by nsDisplayOuterSVG code");
+
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (kid->HasInvalidFrameInSubtree()) {
+ gfxRect r(mRect.x, mRect.y, mRect.width, mRect.height);
+ r.Scale(1.0 / AppUnitsPerCSSPixel());
+ nsRect rect = SVGUtils::ToCanvasBounds(r, GetCanvasTM(), PresContext());
+ rect = SVGUtils::GetPostFilterInkOverflowRect(this, rect);
+ return rect;
+ }
+ return nsRect();
+}
+
+void SVGForeignObjectFrame::AppendDirectlyOwnedAnonBoxes(
+ nsTArray<OwnedAnonBox>& aResult) {
+ MOZ_ASSERT(PrincipalChildList().FirstChild(), "Must have our anon box");
+ aResult.AppendElement(OwnedAnonBox(PrincipalChildList().FirstChild()));
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGForeignObjectFrame.h b/layout/svg/SVGForeignObjectFrame.h
new file mode 100644
index 0000000000..4fcd6fe963
--- /dev/null
+++ b/layout/svg/SVGForeignObjectFrame.h
@@ -0,0 +1,108 @@
+/* -*- 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 LAYOUT_SVG_SVGFOREIGNOBJECTFRAME_H_
+#define LAYOUT_SVG_SVGFOREIGNOBJECTFRAME_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ISVGDisplayableFrame.h"
+#include "mozilla/PresShellForwards.h"
+#include "mozilla/UniquePtr.h"
+#include "nsContainerFrame.h"
+#include "nsRegion.h"
+
+class gfxContext;
+
+nsContainerFrame* NS_NewSVGForeignObjectFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGForeignObjectFrame final : public nsContainerFrame,
+ public ISVGDisplayableFrame {
+ friend nsContainerFrame* ::NS_NewSVGForeignObjectFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ protected:
+ explicit SVGForeignObjectFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext);
+
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(SVGForeignObjectFrame)
+
+ // nsIFrame:
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+ void DestroyFrom(nsIFrame* aDestructRoot,
+ PostDestroyData& aPostDestroyData) override;
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ nsContainerFrame* GetContentInsertionFrame() override {
+ return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
+ }
+
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ bool IsFrameOfType(uint32_t aFlags) const override {
+ if (aFlags & eSupportsContainLayoutAndPaint) {
+ return false;
+ }
+
+ return nsContainerFrame::IsFrameOfType(aFlags & ~nsIFrame::eSVG);
+ }
+
+ bool IsSVGTransformed(Matrix* aOwnTransform,
+ Matrix* aFromParentTransform) const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGForeignObject"_ns, aResult);
+ }
+#endif
+
+ // ISVGDisplayableFrame interface:
+ void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect = nullptr) override;
+ nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
+ void ReflowSVG() override;
+ void NotifySVGChanged(uint32_t aFlags) override;
+ SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace,
+ uint32_t aFlags) override;
+ bool IsDisplayContainer() override { return true; }
+
+ gfxMatrix GetCanvasTM();
+
+ nsRect GetInvalidRegion();
+
+ // Return our ::-moz-svg-foreign-content anonymous box.
+ void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override;
+
+ void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override;
+
+ protected:
+ // implementation helpers:
+ void DoReflow();
+ void RequestReflow(IntrinsicDirty aType);
+
+ // If width or height is less than or equal to zero we must disable rendering
+ bool IsDisabled() const { return mRect.width <= 0 || mRect.height <= 0; }
+
+ UniquePtr<gfxMatrix> mCanvasTM;
+
+ bool mInReflow;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGFOREIGNOBJECTFRAME_H_
diff --git a/layout/svg/SVGGFrame.cpp b/layout/svg/SVGGFrame.cpp
new file mode 100644
index 0000000000..92df721ffb
--- /dev/null
+++ b/layout/svg/SVGGFrame.cpp
@@ -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/. */
+
+// Main header first:
+#include "SVGGFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "mozilla/dom/SVGElement.h"
+#include "mozilla/PresShell.h"
+#include "nsGkAtoms.h"
+#include "nsIFrame.h"
+
+using namespace mozilla::dom;
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsIFrame* NS_NewSVGGFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGGFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGGFrame)
+
+#ifdef DEBUG
+void SVGGFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement() &&
+ static_cast<SVGElement*>(aContent)->IsTransformable(),
+ "The element is not transformable");
+
+ SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+//----------------------------------------------------------------------
+// ISVGDisplayableFrame methods
+
+nsresult SVGGFrame::AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) {
+ if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::transform) {
+ // We don't invalidate for transform changes (the layers code does that).
+ // Also note that SVGTransformableElement::GetAttributeChangeHint will
+ // return nsChangeHint_UpdateOverflow for "transform" attribute changes
+ // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
+ NotifySVGChanged(TRANSFORM_CHANGED);
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGGFrame.h b/layout/svg/SVGGFrame.h
new file mode 100644
index 0000000000..68e01ec8b9
--- /dev/null
+++ b/layout/svg/SVGGFrame.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 LAYOUT_SVG_SVGGFRAME_H_
+#define LAYOUT_SVG_SVGGFRAME_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/SVGContainerFrame.h"
+#include "gfxMatrix.h"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGGFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGGFrame : public SVGDisplayContainerFrame {
+ friend nsIFrame* ::NS_NewSVGGFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+ explicit SVGGFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : SVGGFrame(aStyle, aPresContext, kClassID) {}
+
+ protected:
+ SVGGFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ nsIFrame::ClassID aID)
+ : SVGDisplayContainerFrame(aStyle, aPresContext, aID) {}
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGGFrame)
+
+#ifdef DEBUG
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGG"_ns, aResult);
+ }
+#endif
+
+ // nsIFrame interface:
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGGFRAME_H_
diff --git a/layout/svg/SVGGeometryFrame.cpp b/layout/svg/SVGGeometryFrame.cpp
new file mode 100644
index 0000000000..7d52bd4ff5
--- /dev/null
+++ b/layout/svg/SVGGeometryFrame.cpp
@@ -0,0 +1,898 @@
+/* -*- 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:
+#include "SVGGeometryFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "mozilla/dom/SVGGeometryElement.h"
+#include "mozilla/dom/SVGGraphicsElement.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SVGContextPaint.h"
+#include "mozilla/SVGContentUtils.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGUtils.h"
+#include "nsDisplayList.h"
+#include "nsGkAtoms.h"
+#include "nsLayoutUtils.h"
+#include "SVGAnimatedTransformList.h"
+#include "SVGMarkerFrame.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsIFrame* NS_NewSVGGeometryFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGGeometryFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGGeometryFrame)
+
+//----------------------------------------------------------------------
+// nsQueryFrame methods
+
+NS_QUERYFRAME_HEAD(SVGGeometryFrame)
+ NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame)
+ NS_QUERYFRAME_ENTRY(SVGGeometryFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsIFrame)
+
+void DisplaySVGGeometry::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect, HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) {
+ SVGGeometryFrame* frame = static_cast<SVGGeometryFrame*>(mFrame);
+ nsPoint pointRelativeToReferenceFrame = aRect.Center();
+ // ToReferenceFrame() includes frame->GetPosition(), our user space position.
+ nsPoint userSpacePtInAppUnits = pointRelativeToReferenceFrame -
+ (ToReferenceFrame() - frame->GetPosition());
+ gfxPoint userSpacePt =
+ gfxPoint(userSpacePtInAppUnits.x, userSpacePtInAppUnits.y) /
+ AppUnitsPerCSSPixel();
+ if (frame->GetFrameForPoint(userSpacePt)) {
+ aOutFrames->AppendElement(frame);
+ }
+}
+
+void DisplaySVGGeometry::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ uint32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+
+ // ToReferenceFrame includes our mRect offset, but painting takes
+ // account of that too. To avoid double counting, we subtract that
+ // here.
+ nsPoint offset = ToReferenceFrame() - mFrame->GetPosition();
+
+ gfxPoint devPixelOffset =
+ nsLayoutUtils::PointToGfxPoint(offset, appUnitsPerDevPixel);
+
+ gfxMatrix tm = SVGUtils::GetCSSPxToDevPxMatrix(mFrame) *
+ gfxMatrix::Translation(devPixelOffset);
+ imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags());
+ static_cast<SVGGeometryFrame*>(mFrame)->PaintSVG(*aCtx, tm, imgParams);
+}
+
+//----------------------------------------------------------------------
+// nsIFrame methods
+
+void SVGGeometryFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD);
+ nsIFrame::Init(aContent, aParent, aPrevInFlow);
+ AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
+}
+
+nsresult SVGGeometryFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ // We don't invalidate for transform changes (the layers code does that).
+ // Also note that SVGTransformableElement::GetAttributeChangeHint will
+ // return nsChangeHint_UpdateOverflow for "transform" attribute changes
+ // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
+
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (static_cast<SVGGeometryElement*>(GetContent())
+ ->AttributeDefinesGeometry(aAttribute))) {
+ nsLayoutUtils::PostRestyleEvent(mContent->AsElement(), RestyleHint{0},
+ nsChangeHint_InvalidateRenderingObservers);
+ SVGUtils::ScheduleReflowSVG(this);
+ }
+ return NS_OK;
+}
+
+/* virtual */
+void SVGGeometryFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
+ nsIFrame::DidSetComputedStyle(aOldComputedStyle);
+ auto* element = static_cast<SVGGeometryElement*>(GetContent());
+ if (!aOldComputedStyle) {
+ element->ClearAnyCachedPath();
+ return;
+ }
+
+ const auto* oldStyleSVG = aOldComputedStyle->StyleSVG();
+ if (!SVGContentUtils::ShapeTypeHasNoCorners(GetContent())) {
+ if (StyleSVG()->mStrokeLinecap != oldStyleSVG->mStrokeLinecap &&
+ element->IsSVGElement(nsGkAtoms::path)) {
+ // If the stroke-linecap changes to or from "butt" then our element
+ // needs to update its cached Moz2D Path, since SVGPathData::BuildPath
+ // decides whether or not to insert little lines into the path for zero
+ // length subpaths base on that property.
+ element->ClearAnyCachedPath();
+ } else if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) {
+ if (StyleSVG()->mClipRule != oldStyleSVG->mClipRule) {
+ // Moz2D Path objects are fill-rule specific.
+ // For clipPath we use clip-rule as the path's fill-rule.
+ element->ClearAnyCachedPath();
+ }
+ } else {
+ if (StyleSVG()->mFillRule != oldStyleSVG->mFillRule) {
+ // Moz2D Path objects are fill-rule specific.
+ element->ClearAnyCachedPath();
+ }
+ }
+ }
+
+ if (element->IsGeometryChangedViaCSS(*Style(), *aOldComputedStyle)) {
+ element->ClearAnyCachedPath();
+ }
+}
+
+bool SVGGeometryFrame::IsSVGTransformed(
+ gfx::Matrix* aOwnTransform, gfx::Matrix* aFromParentTransform) const {
+ bool foundTransform = false;
+
+ // Check if our parent has children-only transforms:
+ nsIFrame* parent = GetParent();
+ if (parent &&
+ parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
+ foundTransform =
+ static_cast<SVGContainerFrame*>(parent)->HasChildrenOnlyTransform(
+ aFromParentTransform);
+ }
+
+ SVGElement* content = static_cast<SVGElement*>(GetContent());
+ SVGAnimatedTransformList* transformList = content->GetAnimatedTransformList();
+ if ((transformList && transformList->HasTransform()) ||
+ content->GetAnimateMotionTransform()) {
+ if (aOwnTransform) {
+ *aOwnTransform = gfx::ToMatrix(
+ content->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent));
+ }
+ foundTransform = true;
+ }
+ return foundTransform;
+}
+
+void SVGGeometryFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (!static_cast<const SVGElement*>(GetContent())->HasValidDimensions()) {
+ return;
+ }
+
+ if (aBuilder->IsForPainting()) {
+ if (!IsVisibleForPainting()) {
+ return;
+ }
+ if (StyleEffects()->mOpacity == 0.0f) {
+ return;
+ }
+ const auto* styleSVG = StyleSVG();
+ if (Type() != LayoutFrameType::SVGImage && styleSVG->mFill.kind.IsNone() &&
+ styleSVG->mStroke.kind.IsNone() && styleSVG->mMarkerEnd.IsNone() &&
+ styleSVG->mMarkerMid.IsNone() && styleSVG->mMarkerStart.IsNone()) {
+ return;
+ }
+
+ aBuilder->BuildCompositorHitTestInfoIfNeeded(this,
+ aLists.BorderBackground());
+ }
+
+ DisplayOutline(aBuilder, aLists);
+ aLists.Content()->AppendNewToTop<DisplaySVGGeometry>(aBuilder, this);
+}
+
+//----------------------------------------------------------------------
+// ISVGDisplayableFrame methods
+
+void SVGGeometryFrame::PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect) {
+ if (!StyleVisibility()->IsVisible()) {
+ return;
+ }
+
+ // Matrix to the geometry's user space:
+ gfxMatrix newMatrix =
+ aContext.CurrentMatrixDouble().PreMultiply(aTransform).NudgeToIntegers();
+ if (newMatrix.IsSingular()) {
+ return;
+ }
+
+ uint32_t paintOrder = StyleSVG()->mPaintOrder;
+ if (!paintOrder) {
+ Render(&aContext, eRenderFill | eRenderStroke, newMatrix, aImgParams);
+ PaintMarkers(aContext, aTransform, aImgParams);
+ } else {
+ while (paintOrder) {
+ auto component = StylePaintOrder(paintOrder & kPaintOrderMask);
+ switch (component) {
+ case StylePaintOrder::Fill:
+ Render(&aContext, eRenderFill, newMatrix, aImgParams);
+ break;
+ case StylePaintOrder::Stroke:
+ Render(&aContext, eRenderStroke, newMatrix, aImgParams);
+ break;
+ case StylePaintOrder::Markers:
+ PaintMarkers(aContext, aTransform, aImgParams);
+ break;
+ default:
+ MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?");
+ case StylePaintOrder::Normal:
+ break;
+ }
+ paintOrder >>= kPaintOrderShift;
+ }
+ }
+}
+
+nsIFrame* SVGGeometryFrame::GetFrameForPoint(const gfxPoint& aPoint) {
+ FillRule fillRule;
+ uint16_t hitTestFlags;
+ if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) {
+ hitTestFlags = SVG_HIT_TEST_FILL;
+ fillRule = SVGUtils::ToFillRule(StyleSVG()->mClipRule);
+ } else {
+ hitTestFlags = GetHitTestFlags();
+ if (!hitTestFlags) {
+ return nullptr;
+ }
+ if (hitTestFlags & SVG_HIT_TEST_CHECK_MRECT) {
+ gfxRect rect = nsLayoutUtils::RectToGfxRect(mRect, AppUnitsPerCSSPixel());
+ if (!rect.Contains(aPoint)) {
+ return nullptr;
+ }
+ }
+ fillRule = SVGUtils::ToFillRule(StyleSVG()->mFillRule);
+ }
+
+ bool isHit = false;
+
+ SVGGeometryElement* content = static_cast<SVGGeometryElement*>(GetContent());
+
+ // Using ScreenReferenceDrawTarget() opens us to Moz2D backend specific hit-
+ // testing bugs. Maybe we should use a BackendType::CAIRO DT for hit-testing
+ // so that we get more consistent/backwards compatible results?
+ RefPtr<DrawTarget> drawTarget =
+ gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+ RefPtr<Path> path = content->GetOrBuildPath(drawTarget, fillRule);
+ if (!path) {
+ return nullptr; // no path, so we don't paint anything that can be hit
+ }
+
+ if (hitTestFlags & SVG_HIT_TEST_FILL) {
+ isHit = path->ContainsPoint(ToPoint(aPoint), Matrix());
+ }
+ if (!isHit && (hitTestFlags & SVG_HIT_TEST_STROKE)) {
+ Point point = ToPoint(aPoint);
+ SVGContentUtils::AutoStrokeOptions stroke;
+ SVGContentUtils::GetStrokeOptions(&stroke, content, Style(), nullptr);
+ gfxMatrix userToOuterSVG;
+ if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
+ // We need to transform the path back into the appropriate ancestor
+ // coordinate system in order for non-scaled stroke to be correct.
+ // Naturally we also need to transform the point into the same
+ // coordinate system in order to hit-test against the path.
+ point = ToMatrix(userToOuterSVG).TransformPoint(point);
+ RefPtr<PathBuilder> builder =
+ path->TransformedCopyToBuilder(ToMatrix(userToOuterSVG), fillRule);
+ path = builder->Finish();
+ }
+ isHit = path->StrokeContainsPoint(stroke, point, Matrix());
+ }
+
+ if (isHit && SVGUtils::HitTestClip(this, aPoint)) {
+ return this;
+ }
+
+ return nullptr;
+}
+
+void SVGGeometryFrame::ReflowSVG() {
+ NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
+ "This call is probably a wasteful mistake");
+
+ MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
+ "ReflowSVG mechanism not designed for this");
+
+ if (!SVGUtils::NeedsReflowSVG(this)) {
+ return;
+ }
+
+ uint32_t flags = SVGUtils::eBBoxIncludeFill | SVGUtils::eBBoxIncludeStroke |
+ SVGUtils::eBBoxIncludeMarkers;
+ // Our "visual" overflow rect needs to be valid for building display lists
+ // for hit testing, which means that for certain values of 'pointer-events'
+ // it needs to include the geometry of the fill or stroke even when the fill/
+ // stroke don't actually render (e.g. when stroke="none" or
+ // stroke-opacity="0"). GetHitTestFlags() accounts for 'pointer-events'.
+ uint16_t hitTestFlags = GetHitTestFlags();
+ if ((hitTestFlags & SVG_HIT_TEST_FILL)) {
+ flags |= SVGUtils::eBBoxIncludeFillGeometry;
+ }
+ if ((hitTestFlags & SVG_HIT_TEST_STROKE)) {
+ flags |= SVGUtils::eBBoxIncludeStrokeGeometry;
+ }
+
+ gfxRect extent = GetBBoxContribution(Matrix(), flags).ToThebesRect();
+ mRect = nsLayoutUtils::RoundGfxRectToAppRect(extent, AppUnitsPerCSSPixel());
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ // Make sure we have our filter property (if any) before calling
+ // FinishAndStoreOverflow (subsequent filter changes are handled off
+ // nsChangeHint_UpdateEffects):
+ SVGObserverUtils::UpdateEffects(this);
+ }
+
+ nsRect overflow = nsRect(nsPoint(0, 0), mRect.Size());
+ OverflowAreas overflowAreas(overflow, overflow);
+ FinishAndStoreOverflow(overflowAreas, mRect.Size());
+
+ RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ // Invalidate, but only if this is not our first reflow (since if it is our
+ // first reflow then we haven't had our first paint yet).
+ if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ InvalidateFrame();
+ }
+}
+
+void SVGGeometryFrame::NotifySVGChanged(uint32_t aFlags) {
+ MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
+ "Invalidation logic may need adjusting");
+
+ // Changes to our ancestors may affect how we render when we are rendered as
+ // part of our ancestor (specifically, if our coordinate context changes size
+ // and we have percentage lengths defining our geometry, then we need to be
+ // reflowed). However, ancestor changes cannot affect how we render when we
+ // are rendered as part of any rendering observers that we may have.
+ // Therefore no need to notify rendering observers here.
+
+ // Don't try to be too smart trying to avoid the ScheduleReflowSVG calls
+ // for the stroke properties examined below. Checking HasStroke() is not
+ // enough, since what we care about is whether we include the stroke in our
+ // overflow rects or not, and we sometimes deliberately include stroke
+ // when it's not visible. See the complexities of GetBBoxContribution.
+
+ if (aFlags & COORD_CONTEXT_CHANGED) {
+ auto* geom = static_cast<SVGGeometryElement*>(GetContent());
+ // Stroke currently contributes to our mRect, which is why we have to take
+ // account of stroke-width here. Note that we do not need to take account
+ // of stroke-dashoffset since, although that can have a percentage value
+ // that is resolved against our coordinate context, it does not affect our
+ // mRect.
+ const auto& strokeWidth = StyleSVG()->mStrokeWidth;
+ if (geom->GeometryDependsOnCoordCtx() ||
+ (strokeWidth.IsLengthPercentage() &&
+ strokeWidth.AsLengthPercentage().HasPercent())) {
+ geom->ClearAnyCachedPath();
+ SVGUtils::ScheduleReflowSVG(this);
+ }
+ }
+
+ if ((aFlags & TRANSFORM_CHANGED) && StyleSVGReset()->HasNonScalingStroke()) {
+ // Stroke currently contributes to our mRect, and our stroke depends on
+ // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|.
+ SVGUtils::ScheduleReflowSVG(this);
+ }
+}
+
+SVGBBox SVGGeometryFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace,
+ uint32_t aFlags) {
+ SVGBBox bbox;
+
+ if (aToBBoxUserspace.IsSingular()) {
+ // XXX ReportToConsole
+ return bbox;
+ }
+
+ if ((aFlags & SVGUtils::eForGetClientRects) &&
+ aToBBoxUserspace.PreservesAxisAlignedRectangles()) {
+ Rect rect = NSRectToRect(mRect, AppUnitsPerCSSPixel());
+ bbox = aToBBoxUserspace.TransformBounds(rect);
+ return bbox;
+ }
+
+ SVGGeometryElement* element = static_cast<SVGGeometryElement*>(GetContent());
+
+ bool getFill = (aFlags & SVGUtils::eBBoxIncludeFillGeometry) ||
+ ((aFlags & SVGUtils::eBBoxIncludeFill) &&
+ !StyleSVG()->mFill.kind.IsNone());
+
+ bool getStroke =
+ (aFlags & SVGUtils::eBBoxIncludeStrokeGeometry) ||
+ ((aFlags & SVGUtils::eBBoxIncludeStroke) && SVGUtils::HasStroke(this));
+
+ SVGContentUtils::AutoStrokeOptions strokeOptions;
+ if (getStroke) {
+ SVGContentUtils::GetStrokeOptions(&strokeOptions, element, Style(), nullptr,
+ SVGContentUtils::eIgnoreStrokeDashing);
+ } else {
+ // Override the default line width of 1.f so that when we call
+ // GetGeometryBounds below the result doesn't include stroke bounds.
+ strokeOptions.mLineWidth = 0.f;
+ }
+
+ Rect simpleBounds;
+ bool gotSimpleBounds = false;
+ gfxMatrix userToOuterSVG;
+ if (getStroke &&
+ SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
+ Matrix moz2dUserToOuterSVG = ToMatrix(userToOuterSVG);
+ if (moz2dUserToOuterSVG.IsSingular()) {
+ return bbox;
+ }
+ gotSimpleBounds = element->GetGeometryBounds(
+ &simpleBounds, strokeOptions, aToBBoxUserspace, &moz2dUserToOuterSVG);
+ } else {
+ gotSimpleBounds = element->GetGeometryBounds(&simpleBounds, strokeOptions,
+ aToBBoxUserspace);
+ }
+
+ if (gotSimpleBounds) {
+ bbox = simpleBounds;
+ } else {
+ // Get the bounds using a Moz2D Path object (more expensive):
+ RefPtr<DrawTarget> tmpDT;
+ tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+
+ FillRule fillRule = SVGUtils::ToFillRule(
+ HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) ? StyleSVG()->mClipRule
+ : StyleSVG()->mFillRule);
+ RefPtr<Path> pathInUserSpace = element->GetOrBuildPath(tmpDT, fillRule);
+ if (!pathInUserSpace) {
+ return bbox;
+ }
+ RefPtr<Path> pathInBBoxSpace;
+ if (aToBBoxUserspace.IsIdentity()) {
+ pathInBBoxSpace = pathInUserSpace;
+ } else {
+ RefPtr<PathBuilder> builder =
+ pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule);
+ pathInBBoxSpace = builder->Finish();
+ if (!pathInBBoxSpace) {
+ return bbox;
+ }
+ }
+
+ // Be careful when replacing the following logic to get the fill and stroke
+ // extents independently (instead of computing the stroke extents from the
+ // path extents). You may think that you can just use the stroke extents if
+ // there is both a fill and a stroke. In reality it's necessary to
+ // calculate both the fill and stroke extents, and take the union of the
+ // two. There are two reasons for this:
+ //
+ // # Due to stroke dashing, in certain cases the fill extents could
+ // actually extend outside the stroke extents.
+ // # If the stroke is very thin, cairo won't paint any stroke, and so the
+ // stroke bounds that it will return will be empty.
+
+ Rect pathBBoxExtents = pathInBBoxSpace->GetBounds();
+ if (!pathBBoxExtents.IsFinite()) {
+ // This can happen in the case that we only have a move-to command in the
+ // path commands, in which case we know nothing gets rendered.
+ return bbox;
+ }
+
+ // Account for fill:
+ if (getFill) {
+ bbox = pathBBoxExtents;
+ }
+
+ // Account for stroke:
+ if (getStroke) {
+#if 0
+ // This disabled code is how we would calculate the stroke bounds using
+ // Moz2D Path::GetStrokedBounds(). Unfortunately at the time of writing
+ // it there are two problems that prevent us from using it.
+ //
+ // First, it seems that some of the Moz2D backends are really dumb. Not
+ // only do some GetStrokeOptions() implementations sometimes
+ // significantly overestimate the stroke bounds, but if an argument is
+ // passed for the aTransform parameter then they just return bounds-of-
+ // transformed-bounds. These two things combined can lead the bounds to
+ // be unacceptably oversized, leading to massive over-invalidation.
+ //
+ // Second, the way we account for non-scaling-stroke by transforming the
+ // path using the transform to the outer-<svg> element is not compatible
+ // with the way that SVGGeometryFrame::Reflow() inserts a scale
+ // into aToBBoxUserspace and then scales the bounds that we return.
+ SVGContentUtils::AutoStrokeOptions strokeOptions;
+ SVGContentUtils::GetStrokeOptions(&strokeOptions, element,
+ Style(), nullptr,
+ SVGContentUtils::eIgnoreStrokeDashing);
+ Rect strokeBBoxExtents;
+ gfxMatrix userToOuterSVG;
+ if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
+ Matrix outerSVGToUser = ToMatrix(userToOuterSVG);
+ outerSVGToUser.Invert();
+ Matrix outerSVGToBBox = aToBBoxUserspace * outerSVGToUser;
+ RefPtr<PathBuilder> builder =
+ pathInUserSpace->TransformedCopyToBuilder(ToMatrix(userToOuterSVG));
+ RefPtr<Path> pathInOuterSVGSpace = builder->Finish();
+ strokeBBoxExtents =
+ pathInOuterSVGSpace->GetStrokedBounds(strokeOptions, outerSVGToBBox);
+ } else {
+ strokeBBoxExtents =
+ pathInUserSpace->GetStrokedBounds(strokeOptions, aToBBoxUserspace);
+ }
+ MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad");
+ bbox.UnionEdges(strokeBBoxExtents);
+#else
+ // For now we just use SVGUtils::PathExtentsToMaxStrokeExtents:
+ gfxRect strokeBBoxExtents = SVGUtils::PathExtentsToMaxStrokeExtents(
+ ThebesRect(pathBBoxExtents), this, ThebesMatrix(aToBBoxUserspace));
+ MOZ_ASSERT(ToRect(strokeBBoxExtents).IsFinite(),
+ "bbox is about to go bad");
+ bbox.UnionEdges(strokeBBoxExtents);
+#endif
+ }
+ }
+
+ // Account for markers:
+ if ((aFlags & SVGUtils::eBBoxIncludeMarkers) != 0 && element->IsMarkable()) {
+ SVGMarkerFrame* markerFrames[SVGMark::eTypeCount];
+ if (SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames)) {
+ nsTArray<SVGMark> marks;
+ element->GetMarkPoints(&marks);
+ if (uint32_t num = marks.Length()) {
+ float strokeWidth = SVGUtils::GetStrokeWidth(this);
+ for (uint32_t i = 0; i < num; i++) {
+ const SVGMark& mark = marks[i];
+ SVGMarkerFrame* frame = markerFrames[mark.type];
+ if (frame) {
+ SVGBBox mbbox = frame->GetMarkBBoxContribution(
+ aToBBoxUserspace, aFlags, this, mark, strokeWidth);
+ MOZ_ASSERT(mbbox.IsFinite(), "bbox is about to go bad");
+ bbox.UnionEdges(mbbox);
+ }
+ }
+ }
+ }
+ }
+
+ return bbox;
+}
+
+//----------------------------------------------------------------------
+// SVGGeometryFrame methods:
+
+gfxMatrix SVGGeometryFrame::GetCanvasTM() {
+ NS_ASSERTION(GetParent(), "null parent");
+
+ auto* parent = static_cast<SVGContainerFrame*>(GetParent());
+ auto* content = static_cast<SVGGraphicsElement*>(GetContent());
+
+ return content->PrependLocalTransformsTo(parent->GetCanvasTM());
+}
+
+void SVGGeometryFrame::Render(gfxContext* aContext, uint32_t aRenderComponents,
+ const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams) {
+ MOZ_ASSERT(!aTransform.IsSingular());
+
+ DrawTarget* drawTarget = aContext->GetDrawTarget();
+
+ MOZ_ASSERT(drawTarget);
+ if (!drawTarget->IsValid()) {
+ return;
+ }
+
+ FillRule fillRule = SVGUtils::ToFillRule(
+ HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) ? StyleSVG()->mClipRule
+ : StyleSVG()->mFillRule);
+
+ SVGGeometryElement* element = static_cast<SVGGeometryElement*>(GetContent());
+
+ AntialiasMode aaMode =
+ (StyleSVG()->mShapeRendering == StyleShapeRendering::Optimizespeed ||
+ StyleSVG()->mShapeRendering == StyleShapeRendering::Crispedges)
+ ? AntialiasMode::NONE
+ : AntialiasMode::SUBPIXEL;
+
+ // We wait as late as possible before setting the transform so that we don't
+ // set it unnecessarily if we return early (it's an expensive operation for
+ // some backends).
+ gfxContextMatrixAutoSaveRestore autoRestoreTransform(aContext);
+ aContext->SetMatrixDouble(aTransform);
+
+ if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) {
+ // We don't complicate this code with GetAsSimplePath since the cost of
+ // masking will dwarf Path creation overhead anyway.
+ RefPtr<Path> path = element->GetOrBuildPath(drawTarget, fillRule);
+ if (path) {
+ ColorPattern white(ToDeviceColor(sRGBColor(1.0f, 1.0f, 1.0f, 1.0f)));
+ drawTarget->Fill(path, white,
+ DrawOptions(1.0f, CompositionOp::OP_OVER, aaMode));
+ }
+ return;
+ }
+
+ SVGGeometryElement::SimplePath simplePath;
+ RefPtr<Path> path;
+
+ element->GetAsSimplePath(&simplePath);
+ if (!simplePath.IsPath()) {
+ path = element->GetOrBuildPath(drawTarget, fillRule);
+ if (!path) {
+ return;
+ }
+ }
+
+ SVGContextPaint* contextPaint =
+ SVGContextPaint::GetContextPaint(GetContent());
+
+ if (aRenderComponents & eRenderFill) {
+ GeneralPattern fillPattern;
+ SVGUtils::MakeFillPatternFor(this, aContext, &fillPattern, aImgParams,
+ contextPaint);
+
+ if (fillPattern.GetPattern()) {
+ DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode);
+ if (simplePath.IsRect()) {
+ drawTarget->FillRect(simplePath.AsRect(), fillPattern, drawOptions);
+ } else if (path) {
+ drawTarget->Fill(path, fillPattern, drawOptions);
+ }
+ }
+ }
+
+ if ((aRenderComponents & eRenderStroke) &&
+ SVGUtils::HasStroke(this, contextPaint)) {
+ // Account for vector-effect:non-scaling-stroke:
+ gfxMatrix userToOuterSVG;
+ if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
+ // A simple Rect can't be transformed with rotate/skew, so let's switch
+ // to using a real path:
+ if (!path) {
+ path = element->GetOrBuildPath(drawTarget, fillRule);
+ if (!path) {
+ return;
+ }
+ simplePath.Reset();
+ }
+ // We need to transform the path back into the appropriate ancestor
+ // coordinate system, and paint it it that coordinate system, in order
+ // for non-scaled stroke to paint correctly.
+ gfxMatrix outerSVGToUser = userToOuterSVG;
+ outerSVGToUser.Invert();
+ aContext->Multiply(outerSVGToUser);
+ RefPtr<PathBuilder> builder =
+ path->TransformedCopyToBuilder(ToMatrix(userToOuterSVG), fillRule);
+ path = builder->Finish();
+ }
+ GeneralPattern strokePattern;
+ SVGUtils::MakeStrokePatternFor(this, aContext, &strokePattern, aImgParams,
+ contextPaint);
+
+ if (strokePattern.GetPattern()) {
+ SVGContentUtils::AutoStrokeOptions strokeOptions;
+ SVGContentUtils::GetStrokeOptions(&strokeOptions,
+ static_cast<SVGElement*>(GetContent()),
+ Style(), contextPaint);
+ // GetStrokeOptions may set the line width to zero as an optimization
+ if (strokeOptions.mLineWidth <= 0) {
+ return;
+ }
+ DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode);
+ if (simplePath.IsRect()) {
+ drawTarget->StrokeRect(simplePath.AsRect(), strokePattern,
+ strokeOptions, drawOptions);
+ } else if (simplePath.IsLine()) {
+ drawTarget->StrokeLine(simplePath.Point1(), simplePath.Point2(),
+ strokePattern, strokeOptions, drawOptions);
+ } else {
+ drawTarget->Stroke(path, strokePattern, strokeOptions, drawOptions);
+ }
+ }
+ }
+}
+
+bool SVGGeometryFrame::IsInvisible() const {
+ if (!StyleVisibility()->IsVisible()) {
+ return false;
+ }
+
+ const nsStyleSVG* style = StyleSVG();
+ SVGContextPaint* contextPaint =
+ SVGContextPaint::GetContextPaint(GetContent());
+
+ // Anything below will round to zero later down the pipeline.
+ float opacity_threshold = 1.0 / 128.0;
+
+ float elemOpacity = StyleEffects()->mOpacity;
+ if (elemOpacity <= opacity_threshold) {
+ return true;
+ }
+
+ if (IsSVGImageFrame()) {
+ return false;
+ }
+
+ if (!style->mFill.kind.IsNone()) {
+ float opacity = SVGUtils::GetOpacity(style->mFillOpacity, contextPaint);
+ if (opacity > opacity_threshold) {
+ return false;
+ }
+ }
+
+ if (!style->mStroke.kind.IsNone()) {
+ float opacity = SVGUtils::GetOpacity(style->mStrokeOpacity, contextPaint);
+ if (opacity > opacity_threshold) {
+ return false;
+ }
+ }
+
+ if (style->mMarkerStart.IsUrl() || style->mMarkerMid.IsUrl() ||
+ style->mMarkerEnd.IsUrl()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool SVGGeometryFrame::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder, DisplaySVGGeometry* aItem,
+ bool aDryRun) {
+ if (!StyleVisibility()->IsVisible()) {
+ return true;
+ }
+
+ SVGGeometryElement* element = static_cast<SVGGeometryElement*>(GetContent());
+
+ SVGGeometryElement::SimplePath simplePath;
+ element->GetAsSimplePath(&simplePath);
+
+ if (!simplePath.IsRect()) {
+ return false;
+ }
+
+ const nsStyleSVG* style = StyleSVG();
+ MOZ_ASSERT(style);
+
+ if (!style->mFill.kind.IsColor()) {
+ return false;
+ }
+
+ switch (style->mFill.kind.tag) {
+ case StyleSVGPaintKind::Tag::Color:
+ break;
+ default:
+ return false;
+ }
+
+ if (!style->mStroke.kind.IsNone()) {
+ return false;
+ }
+
+ if (StyleEffects()->mMixBlendMode != StyleBlend::Normal) {
+ // FIXME: not implemented
+ return false;
+ }
+
+ SVGMarkerFrame* markerFrames[SVGMark::eTypeCount];
+ if (element->IsMarkable() &&
+ SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames)) {
+ // Markers aren't suppported yet.
+ return false;
+ }
+
+ if (!aDryRun) {
+ auto appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
+ float scale = (float)AppUnitsPerCSSPixel() / (float)appUnitsPerDevPx;
+
+ auto rect = simplePath.AsRect();
+ rect.Scale(scale);
+
+ auto offset = LayoutDevicePoint::FromAppUnits(
+ aItem->ToReferenceFrame() - GetPosition(), appUnitsPerDevPx);
+ rect.MoveBy(offset.x, offset.y);
+
+ auto wrRect = wr::ToLayoutRect(rect);
+
+ SVGContextPaint* contextPaint =
+ SVGContextPaint::GetContextPaint(GetContent());
+ // At the moment this code path doesn't support strokes so it fine to
+ // combine the rectangle's opacity (which has to be applied on the result)
+ // of (filling + stroking) with the fill opacity.
+ float elemOpacity = StyleEffects()->mOpacity;
+ float fillOpacity = SVGUtils::GetOpacity(style->mFillOpacity, contextPaint);
+ float opacity = elemOpacity * fillOpacity;
+
+ auto c = nsLayoutUtils::GetColor(this, &nsStyleSVG::mFill);
+ wr::ColorF color{
+ ((float)NS_GET_R(c)) / 255.0f, ((float)NS_GET_G(c)) / 255.0f,
+ ((float)NS_GET_B(c)) / 255.0f, ((float)NS_GET_A(c)) / 255.0f * opacity};
+
+ aBuilder.PushRect(wrRect, wrRect, !aItem->BackfaceIsHidden(), true, false,
+ color);
+ }
+
+ return true;
+}
+
+void SVGGeometryFrame::PaintMarkers(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams) {
+ auto* element = static_cast<SVGGeometryElement*>(GetContent());
+ if (!element->IsMarkable()) {
+ return;
+ }
+ SVGMarkerFrame* markerFrames[SVGMark::eTypeCount];
+ if (!SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames)) {
+ return;
+ }
+ nsTArray<SVGMark> marks;
+ element->GetMarkPoints(&marks);
+ if (marks.IsEmpty()) {
+ return;
+ }
+ float strokeWidth = GetStrokeWidthForMarkers();
+ for (const SVGMark& mark : marks) {
+ if (auto* frame = markerFrames[mark.type]) {
+ frame->PaintMark(aContext, aTransform, this, mark, strokeWidth,
+ aImgParams);
+ }
+ }
+}
+
+float SVGGeometryFrame::GetStrokeWidthForMarkers() {
+ float strokeWidth = SVGUtils::GetStrokeWidth(
+ this, SVGContextPaint::GetContextPaint(GetContent()));
+ gfxMatrix userToOuterSVG;
+ if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
+ // We're not interested in any translation here so we can treat this as
+ // Singular Value Decomposition (SVD) of a 2 x 2 matrix. That would give us
+ // sx and sy values as the X and Y scales. The value we want is the XY
+ // scale i.e. the normalised hypotenuse, which is sqrt(sx^2 + sy^2) /
+ // sqrt(2). If we use the formulae from
+ // https://scicomp.stackexchange.com/a/14103, we discover that the
+ // normalised hypotenuse is simply the square root of the sum of the squares
+ // of all the 2D matrix elements divided by sqrt(2).
+ //
+ // Note that this may need adjusting to support 3D transforms properly.
+
+ strokeWidth /= float(sqrt(userToOuterSVG._11 * userToOuterSVG._11 +
+ userToOuterSVG._12 * userToOuterSVG._12 +
+ userToOuterSVG._21 * userToOuterSVG._21 +
+ userToOuterSVG._22 * userToOuterSVG._22) /
+ M_SQRT2);
+ }
+ return strokeWidth;
+}
+
+uint16_t SVGGeometryFrame::GetHitTestFlags() {
+ return SVGUtils::GetGeometryHitTestFlags(this);
+}
+} // namespace mozilla
diff --git a/layout/svg/SVGGeometryFrame.h b/layout/svg/SVGGeometryFrame.h
new file mode 100644
index 0000000000..efc339eeff
--- /dev/null
+++ b/layout/svg/SVGGeometryFrame.h
@@ -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/. */
+
+#ifndef LAYOUT_SVG_SVGGEOMETRYFRAME_H_
+#define LAYOUT_SVG_SVGGEOMETRYFRAME_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ISVGDisplayableFrame.h"
+#include "gfxMatrix.h"
+#include "gfxRect.h"
+#include "nsDisplayList.h"
+#include "nsIFrame.h"
+#include "nsLiteralString.h"
+#include "nsQueryFrame.h"
+
+namespace mozilla {
+
+class DisplaySVGGeometry;
+class PresShell;
+class SVGGeometryFrame;
+class SVGMarkerObserver;
+
+namespace gfx {
+class DrawTarget;
+} // namespace gfx
+
+namespace image {
+struct imgDrawingParams;
+} // namespace image
+
+} // namespace mozilla
+
+class gfxContext;
+class nsAtom;
+class nsIFrame;
+
+struct nsRect;
+
+nsIFrame* NS_NewSVGGeometryFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGGeometryFrame : public nsIFrame, public ISVGDisplayableFrame {
+ using DrawTarget = gfx::DrawTarget;
+
+ friend nsIFrame* ::NS_NewSVGGeometryFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ friend class DisplaySVGGeometry;
+
+ protected:
+ SVGGeometryFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ nsIFrame::ClassID aID = kClassID)
+ : nsIFrame(aStyle, aPresContext, aID) {
+ AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_MAY_BE_TRANSFORMED);
+ }
+
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(SVGGeometryFrame)
+
+ // nsIFrame interface:
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ bool IsFrameOfType(uint32_t aFlags) const override {
+ if (aFlags & eSupportsContainLayoutAndPaint) {
+ return false;
+ }
+
+ return nsIFrame::IsFrameOfType(aFlags & ~nsIFrame::eSVG);
+ }
+
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override;
+
+ bool IsSVGTransformed(Matrix* aOwnTransforms = nullptr,
+ Matrix* aFromParentTransforms = nullptr) const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGGeometry"_ns, aResult);
+ }
+#endif
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ // SVGGeometryFrame methods
+ gfxMatrix GetCanvasTM();
+
+ bool IsInvisible() const;
+
+ protected:
+ // ISVGDisplayableFrame interface:
+ void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect = nullptr) override;
+ nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
+ void ReflowSVG() override;
+ void NotifySVGChanged(uint32_t aFlags) override;
+ SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace,
+ uint32_t aFlags) override;
+ bool IsDisplayContainer() override { return false; }
+
+ /**
+ * This function returns a set of bit flags indicating which parts of the
+ * element (fill, stroke, bounds) should intercept pointer events. It takes
+ * into account the type of element and the value of the 'pointer-events'
+ * property on the element.
+ */
+ virtual uint16_t GetHitTestFlags();
+
+ private:
+ enum { eRenderFill = 1, eRenderStroke = 2 };
+ void Render(gfxContext* aContext, uint32_t aRenderComponents,
+ const gfxMatrix& aTransform, imgDrawingParams& aImgParams);
+
+ virtual bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder, DisplaySVGGeometry* aItem,
+ bool aDryRun);
+ /**
+ * @param aMatrix The transform that must be multiplied onto aContext to
+ * establish this frame's SVG user space.
+ */
+ void PaintMarkers(gfxContext& aContext, const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams);
+
+ /*
+ * Get the stroke width that markers should use, accounting for
+ * non-scaling stroke.
+ */
+ float GetStrokeWidthForMarkers();
+};
+
+//----------------------------------------------------------------------
+// Display list item:
+
+class DisplaySVGGeometry final : public nsPaintedDisplayItem {
+ using imgDrawingParams = image::imgDrawingParams;
+
+ public:
+ DisplaySVGGeometry(nsDisplayListBuilder* aBuilder, SVGGeometryFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(DisplaySVGGeometry);
+ MOZ_ASSERT(aFrame, "Must have a frame!");
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(DisplaySVGGeometry)
+
+ NS_DISPLAY_DECL_NAME("DisplaySVGGeometry", TYPE_SVG_GEOMETRY)
+
+ void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+
+ // Whether this part of the SVG should be natively handled by webrender,
+ // potentially becoming an "active layer" inside a blob image.
+ bool ShouldBeActive(mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ // We delegate this question to the parent frame to take advantage of
+ // the SVGGeometryFrame inheritance hierarchy which provides actual
+ // implementation details. The dryRun flag prevents serious side-effects.
+ auto* frame = static_cast<SVGGeometryFrame*>(mFrame);
+ return frame->CreateWebRenderCommands(aBuilder, aResources, aSc, aManager,
+ aDisplayListBuilder, this,
+ /*aDryRun=*/true);
+ }
+
+ virtual bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override {
+ // We delegate this question to the parent frame to take advantage of
+ // the SVGGeometryFrame inheritance hierarchy which provides actual
+ // implementation details.
+ auto* frame = static_cast<SVGGeometryFrame*>(mFrame);
+ bool result = frame->CreateWebRenderCommands(aBuilder, aResources, aSc,
+ aManager, aDisplayListBuilder,
+ this, /*aDryRun=*/false);
+ MOZ_ASSERT(result, "ShouldBeActive inconsistent with CreateWRCommands?");
+ return result;
+ }
+
+ bool IsInvisible() const override {
+ auto* frame = static_cast<SVGGeometryFrame*>(mFrame);
+ return frame->IsInvisible();
+ }
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGGEOMETRYFRAME_H_
diff --git a/layout/svg/SVGGradientFrame.cpp b/layout/svg/SVGGradientFrame.cpp
new file mode 100644
index 0000000000..42116a9851
--- /dev/null
+++ b/layout/svg/SVGGradientFrame.cpp
@@ -0,0 +1,606 @@
+/* -*- 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:
+#include "SVGGradientFrame.h"
+#include <algorithm>
+
+// Keep others in (case-insensitive) order:
+#include "AutoReferenceChainGuard.h"
+#include "gfxPattern.h"
+#include "gfxUtils.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/SVGGradientElement.h"
+#include "mozilla/dom/SVGGradientElementBinding.h"
+#include "mozilla/dom/SVGStopElement.h"
+#include "mozilla/dom/SVGUnitTypesBinding.h"
+#include "nsContentUtils.h"
+#include "SVGAnimatedTransformList.h"
+
+// XXX Tight coupling with content classes ahead!
+
+using namespace mozilla::dom;
+using namespace mozilla::dom::SVGGradientElement_Binding;
+using namespace mozilla::dom::SVGUnitTypes_Binding;
+using namespace mozilla::gfx;
+
+namespace mozilla {
+
+//----------------------------------------------------------------------
+// Implementation
+
+SVGGradientFrame::SVGGradientFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext, ClassID aID)
+ : SVGPaintServerFrame(aStyle, aPresContext, aID),
+ mSource(nullptr),
+ mLoopFlag(false),
+ mNoHRefURI(false) {}
+
+NS_QUERYFRAME_HEAD(SVGGradientFrame)
+ NS_QUERYFRAME_ENTRY(SVGGradientFrame)
+NS_QUERYFRAME_TAIL_INHERITING(SVGPaintServerFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods:
+
+nsresult SVGGradientFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::gradientUnits ||
+ aAttribute == nsGkAtoms::gradientTransform ||
+ aAttribute == nsGkAtoms::spreadMethod)) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(this);
+ } else if ((aNameSpaceID == kNameSpaceID_XLink ||
+ aNameSpaceID == kNameSpaceID_None) &&
+ aAttribute == nsGkAtoms::href) {
+ // Blow away our reference, if any
+ SVGObserverUtils::RemoveTemplateObserver(this);
+ mNoHRefURI = false;
+ // And update whoever references us
+ SVGObserverUtils::InvalidateDirectRenderingObservers(this);
+ }
+
+ return SVGPaintServerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+}
+
+//----------------------------------------------------------------------
+
+uint16_t SVGGradientFrame::GetEnumValue(uint32_t aIndex, nsIContent* aDefault) {
+ const SVGAnimatedEnumeration& thisEnum =
+ static_cast<dom::SVGGradientElement*>(GetContent())
+ ->mEnumAttributes[aIndex];
+
+ if (thisEnum.IsExplicitlySet()) {
+ return thisEnum.GetAnimValue();
+ }
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return static_cast<dom::SVGGradientElement*>(aDefault)
+ ->mEnumAttributes[aIndex]
+ .GetAnimValue();
+ }
+
+ SVGGradientFrame* next = GetReferencedGradient();
+
+ return next ? next->GetEnumValue(aIndex, aDefault)
+ : static_cast<dom::SVGGradientElement*>(aDefault)
+ ->mEnumAttributes[aIndex]
+ .GetAnimValue();
+}
+
+uint16_t SVGGradientFrame::GetGradientUnits() {
+ // This getter is called every time the others are called - maybe cache it?
+ return GetEnumValue(dom::SVGGradientElement::GRADIENTUNITS);
+}
+
+uint16_t SVGGradientFrame::GetSpreadMethod() {
+ return GetEnumValue(dom::SVGGradientElement::SPREADMETHOD);
+}
+
+const SVGAnimatedTransformList* SVGGradientFrame::GetGradientTransformList(
+ nsIContent* aDefault) {
+ SVGAnimatedTransformList* thisTransformList =
+ static_cast<dom::SVGGradientElement*>(GetContent())
+ ->GetAnimatedTransformList();
+
+ if (thisTransformList && thisTransformList->IsExplicitlySet())
+ return thisTransformList;
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return static_cast<const dom::SVGGradientElement*>(aDefault)
+ ->mGradientTransform.get();
+ }
+
+ SVGGradientFrame* next = GetReferencedGradient();
+
+ return next ? next->GetGradientTransformList(aDefault)
+ : static_cast<const dom::SVGGradientElement*>(aDefault)
+ ->mGradientTransform.get();
+}
+
+gfxMatrix SVGGradientFrame::GetGradientTransform(
+ nsIFrame* aSource, const gfxRect* aOverrideBounds) {
+ gfxMatrix bboxMatrix;
+
+ uint16_t gradientUnits = GetGradientUnits();
+ if (gradientUnits != SVG_UNIT_TYPE_USERSPACEONUSE) {
+ NS_ASSERTION(gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX,
+ "Unknown gradientUnits type");
+ // objectBoundingBox is the default anyway
+
+ gfxRect bbox = aOverrideBounds
+ ? *aOverrideBounds
+ : SVGUtils::GetBBox(
+ aSource, SVGUtils::eUseFrameBoundsForOuterSVG |
+ SVGUtils::eBBoxIncludeFillGeometry);
+ bboxMatrix =
+ gfxMatrix(bbox.Width(), 0, 0, bbox.Height(), bbox.X(), bbox.Y());
+ }
+
+ const SVGAnimatedTransformList* animTransformList =
+ GetGradientTransformList(GetContent());
+ if (!animTransformList) {
+ return bboxMatrix;
+ }
+
+ gfxMatrix gradientTransform =
+ animTransformList->GetAnimValue().GetConsolidationMatrix();
+ return bboxMatrix.PreMultiply(gradientTransform);
+}
+
+dom::SVGLinearGradientElement* SVGGradientFrame::GetLinearGradientWithLength(
+ uint32_t aIndex, dom::SVGLinearGradientElement* aDefault) {
+ // If this was a linear gradient with the required length, we would have
+ // already found it in SVGLinearGradientFrame::GetLinearGradientWithLength.
+ // Since we didn't find the length, continue looking down the chain.
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return aDefault;
+ }
+
+ SVGGradientFrame* next = GetReferencedGradient();
+ return next ? next->GetLinearGradientWithLength(aIndex, aDefault) : aDefault;
+}
+
+dom::SVGRadialGradientElement* SVGGradientFrame::GetRadialGradientWithLength(
+ uint32_t aIndex, dom::SVGRadialGradientElement* aDefault) {
+ // If this was a radial gradient with the required length, we would have
+ // already found it in SVGRadialGradientFrame::GetRadialGradientWithLength.
+ // Since we didn't find the length, continue looking down the chain.
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return aDefault;
+ }
+
+ SVGGradientFrame* next = GetReferencedGradient();
+ return next ? next->GetRadialGradientWithLength(aIndex, aDefault) : aDefault;
+}
+
+//----------------------------------------------------------------------
+// SVGPaintServerFrame methods:
+
+// helper
+static void GetStopInformation(nsIFrame* aStopFrame, float* aOffset,
+ nscolor* aStopColor, float* aStopOpacity) {
+ nsIContent* stopContent = aStopFrame->GetContent();
+ MOZ_ASSERT(stopContent && stopContent->IsSVGElement(nsGkAtoms::stop));
+
+ static_cast<SVGStopElement*>(stopContent)
+ ->GetAnimatedNumberValues(aOffset, nullptr);
+
+ const nsStyleSVGReset* styleSVGReset = aStopFrame->StyleSVGReset();
+ *aOffset = mozilla::clamped(*aOffset, 0.0f, 1.0f);
+ *aStopColor = styleSVGReset->mStopColor.CalcColor(aStopFrame);
+ *aStopOpacity = styleSVGReset->mStopOpacity;
+}
+
+already_AddRefed<gfxPattern> SVGGradientFrame::GetPaintServerPattern(
+ nsIFrame* aSource, const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix, StyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ float aGraphicOpacity, imgDrawingParams& aImgParams,
+ const gfxRect* aOverrideBounds) {
+ uint16_t gradientUnits = GetGradientUnits();
+ MOZ_ASSERT(gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX ||
+ gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE);
+ if (gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE) {
+ // Set mSource for this consumer.
+ // If this gradient is applied to text, our caller will be the glyph, which
+ // is not an element, so we need to get the parent
+ mSource = aSource->GetContent()->IsText() ? aSource->GetParent() : aSource;
+ }
+
+ AutoTArray<nsIFrame*, 8> stopFrames;
+ GetStopFrames(&stopFrames);
+
+ uint32_t nStops = stopFrames.Length();
+
+ // SVG specification says that no stops should be treated like
+ // the corresponding fill or stroke had "none" specified.
+ if (nStops == 0) {
+ RefPtr<gfxPattern> pattern = new gfxPattern(DeviceColor());
+ return do_AddRef(new gfxPattern(DeviceColor()));
+ }
+
+ if (nStops == 1 || GradientVectorLengthIsZero()) {
+ auto* lastStopFrame = stopFrames[nStops - 1];
+ const auto* svgReset = lastStopFrame->StyleSVGReset();
+ // The gradient paints a single colour, using the stop-color of the last
+ // gradient step if there are more than one.
+ float stopOpacity = svgReset->mStopOpacity;
+ nscolor stopColor = svgReset->mStopColor.CalcColor(lastStopFrame);
+
+ sRGBColor stopColor2 = sRGBColor::FromABGR(stopColor);
+ stopColor2.a *= stopOpacity * aGraphicOpacity;
+ return do_AddRef(new gfxPattern(ToDeviceColor(stopColor2)));
+ }
+
+ // Get the transform list (if there is one). We do this after the returns
+ // above since this call can be expensive when "gradientUnits" is set to
+ // "objectBoundingBox" (since that requiring a GetBBox() call).
+ gfxMatrix patternMatrix = GetGradientTransform(aSource, aOverrideBounds);
+
+ if (patternMatrix.IsSingular()) {
+ return nullptr;
+ }
+
+ // revert any vector effect transform so that the gradient appears unchanged
+ if (aFillOrStroke == &nsStyleSVG::mStroke) {
+ gfxMatrix userToOuterSVG;
+ if (SVGUtils::GetNonScalingStrokeTransform(aSource, &userToOuterSVG)) {
+ patternMatrix *= userToOuterSVG;
+ }
+ }
+
+ if (!patternMatrix.Invert()) {
+ return nullptr;
+ }
+
+ RefPtr<gfxPattern> gradient = CreateGradient();
+ if (!gradient) {
+ return nullptr;
+ }
+
+ uint16_t aSpread = GetSpreadMethod();
+ if (aSpread == SVG_SPREADMETHOD_PAD)
+ gradient->SetExtend(ExtendMode::CLAMP);
+ else if (aSpread == SVG_SPREADMETHOD_REFLECT)
+ gradient->SetExtend(ExtendMode::REFLECT);
+ else if (aSpread == SVG_SPREADMETHOD_REPEAT)
+ gradient->SetExtend(ExtendMode::REPEAT);
+
+ gradient->SetMatrix(patternMatrix);
+
+ // setup stops
+ float lastOffset = 0.0f;
+
+ for (uint32_t i = 0; i < nStops; i++) {
+ float offset, stopOpacity;
+ nscolor stopColor;
+
+ GetStopInformation(stopFrames[i], &offset, &stopColor, &stopOpacity);
+
+ if (offset < lastOffset)
+ offset = lastOffset;
+ else
+ lastOffset = offset;
+
+ sRGBColor stopColor2 = sRGBColor::FromABGR(stopColor);
+ stopColor2.a *= stopOpacity * aGraphicOpacity;
+ gradient->AddColorStop(offset, ToDeviceColor(stopColor2));
+ }
+
+ return gradient.forget();
+}
+
+// Private (helper) methods
+
+SVGGradientFrame* SVGGradientFrame::GetReferencedGradient() {
+ if (mNoHRefURI) {
+ return nullptr;
+ }
+
+ auto GetHref = [this](nsAString& aHref) {
+ dom::SVGGradientElement* grad =
+ static_cast<dom::SVGGradientElement*>(this->GetContent());
+ if (grad->mStringAttributes[dom::SVGGradientElement::HREF]
+ .IsExplicitlySet()) {
+ grad->mStringAttributes[dom::SVGGradientElement::HREF].GetAnimValue(aHref,
+ grad);
+ } else {
+ grad->mStringAttributes[dom::SVGGradientElement::XLINK_HREF].GetAnimValue(
+ aHref, grad);
+ }
+ this->mNoHRefURI = aHref.IsEmpty();
+ };
+
+ nsIFrame* tframe = SVGObserverUtils::GetAndObserveTemplate(this, GetHref);
+ if (tframe) {
+ return static_cast<SVGGradientFrame*>(do_QueryFrame(tframe));
+ }
+ // We don't call SVGObserverUtils::RemoveTemplateObserver and set
+ // `mNoHRefURI = false` here since we want to be invalidated if the ID
+ // specified by our href starts resolving to a different/valid element.
+
+ return nullptr;
+}
+
+void SVGGradientFrame::GetStopFrames(nsTArray<nsIFrame*>* aStopFrames) {
+ nsIFrame* stopFrame = nullptr;
+ for (stopFrame = mFrames.FirstChild(); stopFrame;
+ stopFrame = stopFrame->GetNextSibling()) {
+ if (stopFrame->IsSVGStopFrame()) {
+ aStopFrames->AppendElement(stopFrame);
+ }
+ }
+ if (aStopFrames->Length() > 0) {
+ return;
+ }
+
+ // Our gradient element doesn't have stops - try to "inherit" them
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return;
+ }
+
+ SVGGradientFrame* next = GetReferencedGradient();
+ if (next) {
+ next->GetStopFrames(aStopFrames);
+ }
+}
+
+// -------------------------------------------------------------------------
+// Linear Gradients
+// -------------------------------------------------------------------------
+
+NS_QUERYFRAME_HEAD(SVGLinearGradientFrame)
+ NS_QUERYFRAME_ENTRY(SVGLinearGradientFrame)
+NS_QUERYFRAME_TAIL_INHERITING(SVGGradientFrame)
+
+#ifdef DEBUG
+void SVGLinearGradientFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::linearGradient),
+ "Content is not an SVG linearGradient");
+
+ SVGGradientFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsresult SVGLinearGradientFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::x1 || aAttribute == nsGkAtoms::y1 ||
+ aAttribute == nsGkAtoms::x2 || aAttribute == nsGkAtoms::y2)) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(this);
+ }
+
+ return SVGGradientFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+//----------------------------------------------------------------------
+
+float SVGLinearGradientFrame::GetLengthValue(uint32_t aIndex) {
+ dom::SVGLinearGradientElement* lengthElement = GetLinearGradientWithLength(
+ aIndex, static_cast<dom::SVGLinearGradientElement*>(GetContent()));
+ // We passed in mContent as a fallback, so, assuming mContent is non-null, the
+ // return value should also be non-null.
+ MOZ_ASSERT(lengthElement,
+ "Got unexpected null element from GetLinearGradientWithLength");
+ const SVGAnimatedLength& length = lengthElement->mLengthAttributes[aIndex];
+
+ // Object bounding box units are handled by setting the appropriate
+ // transform in GetGradientTransform, but we need to handle user
+ // space units as part of the individual Get* routines. Fixes 323669.
+
+ uint16_t gradientUnits = GetGradientUnits();
+ if (gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE) {
+ return SVGUtils::UserSpace(mSource, &length);
+ }
+
+ NS_ASSERTION(gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX,
+ "Unknown gradientUnits type");
+
+ return length.GetAnimValue(static_cast<SVGViewportElement*>(nullptr));
+}
+
+dom::SVGLinearGradientElement*
+SVGLinearGradientFrame::GetLinearGradientWithLength(
+ uint32_t aIndex, dom::SVGLinearGradientElement* aDefault) {
+ dom::SVGLinearGradientElement* thisElement =
+ static_cast<dom::SVGLinearGradientElement*>(GetContent());
+ const SVGAnimatedLength& length = thisElement->mLengthAttributes[aIndex];
+
+ if (length.IsExplicitlySet()) {
+ return thisElement;
+ }
+
+ return SVGGradientFrame::GetLinearGradientWithLength(aIndex, aDefault);
+}
+
+bool SVGLinearGradientFrame::GradientVectorLengthIsZero() {
+ return GetLengthValue(dom::SVGLinearGradientElement::ATTR_X1) ==
+ GetLengthValue(dom::SVGLinearGradientElement::ATTR_X2) &&
+ GetLengthValue(dom::SVGLinearGradientElement::ATTR_Y1) ==
+ GetLengthValue(dom::SVGLinearGradientElement::ATTR_Y2);
+}
+
+already_AddRefed<gfxPattern> SVGLinearGradientFrame::CreateGradient() {
+ float x1, y1, x2, y2;
+
+ x1 = GetLengthValue(dom::SVGLinearGradientElement::ATTR_X1);
+ y1 = GetLengthValue(dom::SVGLinearGradientElement::ATTR_Y1);
+ x2 = GetLengthValue(dom::SVGLinearGradientElement::ATTR_X2);
+ y2 = GetLengthValue(dom::SVGLinearGradientElement::ATTR_Y2);
+
+ RefPtr<gfxPattern> pattern = new gfxPattern(x1, y1, x2, y2);
+ return pattern.forget();
+}
+
+// -------------------------------------------------------------------------
+// Radial Gradients
+// -------------------------------------------------------------------------
+
+NS_QUERYFRAME_HEAD(SVGRadialGradientFrame)
+ NS_QUERYFRAME_ENTRY(SVGRadialGradientFrame)
+NS_QUERYFRAME_TAIL_INHERITING(SVGGradientFrame)
+
+#ifdef DEBUG
+void SVGRadialGradientFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::radialGradient),
+ "Content is not an SVG radialGradient");
+
+ SVGGradientFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsresult SVGRadialGradientFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::r || aAttribute == nsGkAtoms::cx ||
+ aAttribute == nsGkAtoms::cy || aAttribute == nsGkAtoms::fx ||
+ aAttribute == nsGkAtoms::fy)) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(this);
+ }
+
+ return SVGGradientFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+//----------------------------------------------------------------------
+
+float SVGRadialGradientFrame::GetLengthValue(uint32_t aIndex) {
+ dom::SVGRadialGradientElement* lengthElement = GetRadialGradientWithLength(
+ aIndex, static_cast<dom::SVGRadialGradientElement*>(GetContent()));
+ // We passed in mContent as a fallback, so, assuming mContent is non-null,
+ // the return value should also be non-null.
+ MOZ_ASSERT(lengthElement,
+ "Got unexpected null element from GetRadialGradientWithLength");
+ return GetLengthValueFromElement(aIndex, *lengthElement);
+}
+
+float SVGRadialGradientFrame::GetLengthValue(uint32_t aIndex,
+ float aDefaultValue) {
+ dom::SVGRadialGradientElement* lengthElement =
+ GetRadialGradientWithLength(aIndex, nullptr);
+
+ return lengthElement ? GetLengthValueFromElement(aIndex, *lengthElement)
+ : aDefaultValue;
+}
+
+float SVGRadialGradientFrame::GetLengthValueFromElement(
+ uint32_t aIndex, dom::SVGRadialGradientElement& aElement) {
+ const SVGAnimatedLength& length = aElement.mLengthAttributes[aIndex];
+
+ // Object bounding box units are handled by setting the appropriate
+ // transform in GetGradientTransform, but we need to handle user
+ // space units as part of the individual Get* routines. Fixes 323669.
+
+ uint16_t gradientUnits = GetGradientUnits();
+ if (gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE) {
+ return SVGUtils::UserSpace(mSource, &length);
+ }
+
+ NS_ASSERTION(gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX,
+ "Unknown gradientUnits type");
+
+ return length.GetAnimValue(static_cast<SVGViewportElement*>(nullptr));
+}
+
+dom::SVGRadialGradientElement*
+SVGRadialGradientFrame::GetRadialGradientWithLength(
+ uint32_t aIndex, dom::SVGRadialGradientElement* aDefault) {
+ dom::SVGRadialGradientElement* thisElement =
+ static_cast<dom::SVGRadialGradientElement*>(GetContent());
+ const SVGAnimatedLength& length = thisElement->mLengthAttributes[aIndex];
+
+ if (length.IsExplicitlySet()) {
+ return thisElement;
+ }
+
+ return SVGGradientFrame::GetRadialGradientWithLength(aIndex, aDefault);
+}
+
+bool SVGRadialGradientFrame::GradientVectorLengthIsZero() {
+ return GetLengthValue(dom::SVGRadialGradientElement::ATTR_R) == 0;
+}
+
+already_AddRefed<gfxPattern> SVGRadialGradientFrame::CreateGradient() {
+ float cx, cy, r, fx, fy, fr;
+
+ cx = GetLengthValue(dom::SVGRadialGradientElement::ATTR_CX);
+ cy = GetLengthValue(dom::SVGRadialGradientElement::ATTR_CY);
+ r = GetLengthValue(dom::SVGRadialGradientElement::ATTR_R);
+ // If fx or fy are not set, use cx/cy instead
+ fx = GetLengthValue(dom::SVGRadialGradientElement::ATTR_FX, cx);
+ fy = GetLengthValue(dom::SVGRadialGradientElement::ATTR_FY, cy);
+ fr = GetLengthValue(dom::SVGRadialGradientElement::ATTR_FR);
+
+ RefPtr<gfxPattern> pattern = new gfxPattern(fx, fy, fr, cx, cy, r);
+ return pattern.forget();
+}
+
+} // namespace mozilla
+
+// -------------------------------------------------------------------------
+// Public functions
+// -------------------------------------------------------------------------
+
+nsIFrame* NS_NewSVGLinearGradientFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGLinearGradientFrame(aStyle, aPresShell->GetPresContext());
+}
+
+nsIFrame* NS_NewSVGRadialGradientFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGRadialGradientFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGLinearGradientFrame)
+NS_IMPL_FRAMEARENA_HELPERS(SVGRadialGradientFrame)
+
+} // namespace mozilla
diff --git a/layout/svg/SVGGradientFrame.h b/layout/svg/SVGGradientFrame.h
new file mode 100644
index 0000000000..50b78fb06c
--- /dev/null
+++ b/layout/svg/SVGGradientFrame.h
@@ -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/. */
+
+#ifndef LAYOUT_SVG_SVGGRADIENTFRAME_H_
+#define LAYOUT_SVG_SVGGRADIENTFRAME_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/SVGPaintServerFrame.h"
+#include "gfxMatrix.h"
+#include "gfxRect.h"
+#include "nsCOMPtr.h"
+#include "nsIFrame.h"
+#include "nsLiteralString.h"
+
+class gfxPattern;
+class nsAtom;
+class nsIContent;
+
+namespace mozilla {
+class PresShell;
+class SVGAnimatedTransformList;
+
+namespace dom {
+class SVGLinearGradientElement;
+class SVGRadialGradientElement;
+} // namespace dom
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGLinearGradientFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGRadialGradientFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGGradientFrame : public SVGPaintServerFrame {
+ using ExtendMode = gfx::ExtendMode;
+
+ protected:
+ SVGGradientFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ ClassID aID);
+
+ public:
+ NS_DECL_ABSTRACT_FRAME(SVGGradientFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_QUERYFRAME_TARGET(SVGGradientFrame)
+
+ // SVGPaintServerFrame methods:
+ already_AddRefed<gfxPattern> GetPaintServerPattern(
+ nsIFrame* aSource, const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix, StyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ float aGraphicOpacity, imgDrawingParams& aImgParams,
+ const gfxRect* aOverrideBounds) override;
+
+ // nsIFrame interface:
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGGradient"_ns, aResult);
+ }
+#endif // DEBUG
+
+ private:
+ /**
+ * Parses this frame's href and - if it references another gradient - returns
+ * it. It also makes this frame a rendering observer of the specified ID.
+ */
+ SVGGradientFrame* GetReferencedGradient();
+
+ // Optionally get a stop frame (returns stop index/count)
+ void GetStopFrames(nsTArray<nsIFrame*>* aStopFrames);
+
+ const SVGAnimatedTransformList* GetGradientTransformList(
+ nsIContent* aDefault);
+ // Will be singular for gradientUnits="objectBoundingBox" with an empty bbox.
+ gfxMatrix GetGradientTransform(nsIFrame* aSource,
+ const gfxRect* aOverrideBounds);
+
+ protected:
+ virtual bool GradientVectorLengthIsZero() = 0;
+ virtual already_AddRefed<gfxPattern> CreateGradient() = 0;
+
+ // Accessors to lookup gradient attributes
+ uint16_t GetEnumValue(uint32_t aIndex, nsIContent* aDefault);
+ uint16_t GetEnumValue(uint32_t aIndex) {
+ return GetEnumValue(aIndex, mContent);
+ }
+ uint16_t GetGradientUnits();
+ uint16_t GetSpreadMethod();
+
+ // Gradient-type-specific lookups since the length values differ between
+ // linear and radial gradients
+ virtual dom::SVGLinearGradientElement* GetLinearGradientWithLength(
+ uint32_t aIndex, dom::SVGLinearGradientElement* aDefault);
+ virtual dom::SVGRadialGradientElement* GetRadialGradientWithLength(
+ uint32_t aIndex, dom::SVGRadialGradientElement* aDefault);
+
+ // The frame our gradient is (currently) being applied to
+ nsIFrame* mSource;
+
+ private:
+ // Flag to mark this frame as "in use" during recursive calls along our
+ // gradient's reference chain so we can detect reference loops. See:
+ // http://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementHrefAttribute
+ bool mLoopFlag;
+ // Gradients often don't reference other gradients, so here we cache
+ // the fact that that isn't happening.
+ bool mNoHRefURI;
+};
+
+// -------------------------------------------------------------------------
+// Linear Gradients
+// -------------------------------------------------------------------------
+
+class SVGLinearGradientFrame final : public SVGGradientFrame {
+ friend nsIFrame* ::NS_NewSVGLinearGradientFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ protected:
+ explicit SVGLinearGradientFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : SVGGradientFrame(aStyle, aPresContext, kClassID) {}
+
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(SVGLinearGradientFrame)
+
+ // nsIFrame interface:
+#ifdef DEBUG
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGLinearGradient"_ns, aResult);
+ }
+#endif // DEBUG
+
+ protected:
+ float GetLengthValue(uint32_t aIndex);
+ mozilla::dom::SVGLinearGradientElement* GetLinearGradientWithLength(
+ uint32_t aIndex,
+ mozilla::dom::SVGLinearGradientElement* aDefault) override;
+ bool GradientVectorLengthIsZero() override;
+ already_AddRefed<gfxPattern> CreateGradient() override;
+};
+
+// -------------------------------------------------------------------------
+// Radial Gradients
+// -------------------------------------------------------------------------
+
+class SVGRadialGradientFrame final : public SVGGradientFrame {
+ friend nsIFrame* ::NS_NewSVGRadialGradientFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ protected:
+ explicit SVGRadialGradientFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : SVGGradientFrame(aStyle, aPresContext, kClassID) {}
+
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(SVGRadialGradientFrame)
+
+ // nsIFrame interface:
+#ifdef DEBUG
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGRadialGradient"_ns, aResult);
+ }
+#endif // DEBUG
+
+ protected:
+ float GetLengthValue(uint32_t aIndex);
+ float GetLengthValue(uint32_t aIndex, float aDefaultValue);
+ float GetLengthValueFromElement(
+ uint32_t aIndex, mozilla::dom::SVGRadialGradientElement& aElement);
+ mozilla::dom::SVGRadialGradientElement* GetRadialGradientWithLength(
+ uint32_t aIndex,
+ mozilla::dom::SVGRadialGradientElement* aDefault) override;
+ bool GradientVectorLengthIsZero() override;
+ already_AddRefed<gfxPattern> CreateGradient() override;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGGRADIENTFRAME_H_
diff --git a/layout/svg/SVGImageContext.cpp b/layout/svg/SVGImageContext.cpp
new file mode 100644
index 0000000000..ff84e9e8c6
--- /dev/null
+++ b/layout/svg/SVGImageContext.cpp
@@ -0,0 +1,88 @@
+/* -*- 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:
+#include "SVGImageContext.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfxUtils.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/StaticPrefs_svg.h"
+#include "mozilla/dom/Document.h"
+#include "nsIFrame.h"
+#include "nsPresContext.h"
+#include "nsStyleStruct.h"
+
+namespace mozilla {
+
+/* static */
+void SVGImageContext::MaybeStoreContextPaint(SVGImageContext& aContext,
+ nsIFrame* aFromFrame,
+ imgIContainer* aImgContainer) {
+ return MaybeStoreContextPaint(aContext, *aFromFrame->PresContext(),
+ *aFromFrame->Style(), aImgContainer);
+}
+
+/* static */
+void SVGImageContext::MaybeStoreContextPaint(SVGImageContext& aContext,
+ const nsPresContext& aPresContext,
+ const ComputedStyle& aStyle,
+ imgIContainer* aImgContainer) {
+ if (aImgContainer->GetType() != imgIContainer::TYPE_VECTOR) {
+ // Avoid this overhead for raster images.
+ return;
+ }
+
+ if (StaticPrefs::svg_embedder_prefers_color_scheme_content_enabled() ||
+ aPresContext.Document()->IsDocumentURISchemeChrome()) {
+ auto scheme = LookAndFeel::ColorSchemeForStyle(
+ *aPresContext.Document(), aStyle.StyleUI()->mColorScheme.bits,
+ ColorSchemeMode::Preferred);
+ aContext.SetColorScheme(Some(scheme));
+ }
+
+ const nsStyleSVG* style = aStyle.StyleSVG();
+ if (!style->ExposesContextProperties()) {
+ // Content must have '-moz-context-properties' set to the names of the
+ // properties it wants to expose to images it links to.
+ return;
+ }
+
+ bool haveContextPaint = false;
+
+ auto contextPaint = MakeRefPtr<SVGEmbeddingContextPaint>();
+
+ if ((style->mMozContextProperties.bits & StyleContextPropertyBits::FILL) &&
+ style->mFill.kind.IsColor()) {
+ haveContextPaint = true;
+ contextPaint->SetFill(style->mFill.kind.AsColor().CalcColor(aStyle));
+ }
+ if ((style->mMozContextProperties.bits & StyleContextPropertyBits::STROKE) &&
+ style->mStroke.kind.IsColor()) {
+ haveContextPaint = true;
+ contextPaint->SetStroke(style->mStroke.kind.AsColor().CalcColor(aStyle));
+ }
+ if (style->mMozContextProperties.bits &
+ StyleContextPropertyBits::FILL_OPACITY) {
+ haveContextPaint = true;
+ contextPaint->SetFillOpacity(style->mFillOpacity.IsOpacity()
+ ? style->mFillOpacity.AsOpacity()
+ : 1.0f);
+ }
+ if (style->mMozContextProperties.bits &
+ StyleContextPropertyBits::STROKE_OPACITY) {
+ haveContextPaint = true;
+ contextPaint->SetStrokeOpacity(style->mStrokeOpacity.IsOpacity()
+ ? style->mStrokeOpacity.AsOpacity()
+ : 1.0f);
+ }
+
+ if (haveContextPaint) {
+ aContext.mContextPaint = std::move(contextPaint);
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGImageContext.h b/layout/svg/SVGImageContext.h
new file mode 100644
index 0000000000..e40e41d16b
--- /dev/null
+++ b/layout/svg/SVGImageContext.h
@@ -0,0 +1,136 @@
+/* -*- 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 LAYOUT_SVG_SVGIMAGECONTEXT_H_
+#define LAYOUT_SVG_SVGIMAGECONTEXT_H_
+
+#include "mozilla/Maybe.h"
+#include "mozilla/SVGContextPaint.h"
+#include "mozilla/SVGPreserveAspectRatio.h"
+#include "Units.h"
+
+class nsIFrame;
+
+namespace mozilla {
+
+enum class ColorScheme : uint8_t;
+class ComputedStyle;
+
+// SVG image-specific rendering context. For imgIContainer::Draw.
+// Used to pass information such as
+// - viewport and color-scheme information from CSS, and
+// - overridden attributes from an SVG <image> element
+// to the image's internal SVG document when it's drawn.
+class SVGImageContext {
+ public:
+ SVGImageContext() = default;
+
+ /**
+ * Currently it seems that the aViewportSize parameter ends up being used
+ * for different things by different pieces of code, and probably in some
+ * cases being used incorrectly (specifically in the case of pixel snapping
+ * under the nsLayoutUtils::Draw*Image() methods). An unfortunate result of
+ * the messy code is that aViewportSize is currently a Maybe<T> since it
+ * is difficult to create a utility function that consumers can use up
+ * front to get the "correct" viewport size (i.e. which for compatibility
+ * with the current code (bugs and all) would mean the size including all
+ * the snapping and resizing magic that happens in various places under the
+ * nsLayoutUtils::Draw*Image() methods on the way to DrawImageInternal
+ * creating |fallbackContext|). Using Maybe<T> allows code to pass Nothing()
+ * in order to get the size that's created for |fallbackContext|. At some
+ * point we need to clean this code up, make our abstractions clear, create
+ * that utility and stop using Maybe for this parameter.
+ */
+ explicit SVGImageContext(
+ const Maybe<CSSIntSize>& aViewportSize,
+ const Maybe<SVGPreserveAspectRatio>& aPreserveAspectRatio = Nothing(),
+ const Maybe<ColorScheme>& aColorScheme = Nothing())
+ : mViewportSize(aViewportSize),
+ mPreserveAspectRatio(aPreserveAspectRatio),
+ mColorScheme(aColorScheme) {}
+
+ static void MaybeStoreContextPaint(SVGImageContext& aContext,
+ nsIFrame* aFromFrame,
+ imgIContainer* aImgContainer);
+
+ static void MaybeStoreContextPaint(SVGImageContext& aContext,
+ const nsPresContext&, const ComputedStyle&,
+ imgIContainer*);
+
+ const Maybe<CSSIntSize>& GetViewportSize() const { return mViewportSize; }
+
+ void SetViewportSize(const Maybe<CSSIntSize>& aSize) {
+ mViewportSize = aSize;
+ }
+
+ const Maybe<ColorScheme>& GetColorScheme() const { return mColorScheme; }
+
+ void SetColorScheme(const Maybe<ColorScheme>& aScheme) {
+ mColorScheme = aScheme;
+ }
+
+ const Maybe<SVGPreserveAspectRatio>& GetPreserveAspectRatio() const {
+ return mPreserveAspectRatio;
+ }
+
+ void SetPreserveAspectRatio(const Maybe<SVGPreserveAspectRatio>& aPAR) {
+ mPreserveAspectRatio = aPAR;
+ }
+
+ const SVGEmbeddingContextPaint* GetContextPaint() const {
+ return mContextPaint.get();
+ }
+
+ void ClearContextPaint() { mContextPaint = nullptr; }
+
+ bool operator==(const SVGImageContext& aOther) const {
+ bool contextPaintIsEqual =
+ // neither have context paint, or they have the same object:
+ (mContextPaint == aOther.mContextPaint) ||
+ // or both have context paint that are different but equivalent objects:
+ (mContextPaint && aOther.mContextPaint &&
+ *mContextPaint == *aOther.mContextPaint);
+
+ return contextPaintIsEqual && mViewportSize == aOther.mViewportSize &&
+ mPreserveAspectRatio == aOther.mPreserveAspectRatio &&
+ mColorScheme == aOther.mColorScheme;
+ }
+
+ bool operator!=(const SVGImageContext& aOther) const {
+ return !(*this == aOther);
+ }
+
+ PLDHashNumber Hash() const {
+ PLDHashNumber hash = 0;
+ if (mContextPaint) {
+ hash = HashGeneric(hash, mContextPaint->Hash());
+ }
+ return HashGeneric(hash, mViewportSize.map(HashSize).valueOr(0),
+ mPreserveAspectRatio.map(HashPAR).valueOr(0),
+ mColorScheme.map(HashColorScheme).valueOr(0));
+ }
+
+ private:
+ static PLDHashNumber HashSize(const CSSIntSize& aSize) {
+ return HashGeneric(aSize.width, aSize.height);
+ }
+ static PLDHashNumber HashPAR(const SVGPreserveAspectRatio& aPAR) {
+ return aPAR.Hash();
+ }
+ static PLDHashNumber HashColorScheme(ColorScheme aScheme) {
+ return HashGeneric(uint8_t(aScheme));
+ }
+
+ // NOTE: When adding new member-vars, remember to update Hash() & operator==.
+ RefPtr<SVGEmbeddingContextPaint> mContextPaint;
+ Maybe<CSSIntSize> mViewportSize;
+ Maybe<SVGPreserveAspectRatio> mPreserveAspectRatio;
+ Maybe<ColorScheme> mColorScheme;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGIMAGECONTEXT_H_
diff --git a/layout/svg/SVGImageFrame.cpp b/layout/svg/SVGImageFrame.cpp
new file mode 100644
index 0000000000..67975ac122
--- /dev/null
+++ b/layout/svg/SVGImageFrame.cpp
@@ -0,0 +1,880 @@
+/* -*- 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 "SVGImageFrame.h"
+
+// Keep in (case-insensitive) order:
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/image/WebRenderImageProvider.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "imgIContainer.h"
+#include "ImageRegion.h"
+#include "nsContainerFrame.h"
+#include "nsIImageLoadingContent.h"
+#include "nsLayoutUtils.h"
+#include "imgINotificationObserver.h"
+#include "SVGGeometryProperty.h"
+#include "SVGGeometryFrame.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_image.h"
+#include "mozilla/SVGContentUtils.h"
+#include "mozilla/SVGImageContext.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/MutationEventBinding.h"
+#include "mozilla/dom/SVGImageElement.h"
+#include "nsIReflowCallback.h"
+#include "mozilla/Unused.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+using namespace mozilla::dom::SVGPreserveAspectRatio_Binding;
+namespace SVGT = SVGGeometryProperty::Tags;
+
+namespace mozilla {
+
+class SVGImageListener final : public imgINotificationObserver {
+ public:
+ explicit SVGImageListener(SVGImageFrame* aFrame);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+
+ void SetFrame(SVGImageFrame* frame) { mFrame = frame; }
+
+ private:
+ ~SVGImageListener() = default;
+
+ SVGImageFrame* mFrame;
+};
+
+// ---------------------------------------------------------------------
+// nsQueryFrame methods
+NS_QUERYFRAME_HEAD(SVGImageFrame)
+ NS_QUERYFRAME_ENTRY(SVGImageFrame)
+NS_QUERYFRAME_TAIL_INHERITING(SVGGeometryFrame)
+
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGImageFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGImageFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGImageFrame)
+
+SVGImageFrame::~SVGImageFrame() {
+ // set the frame to null so we don't send messages to a dead object.
+ if (mListener) {
+ nsCOMPtr<nsIImageLoadingContent> imageLoader =
+ do_QueryInterface(GetContent());
+ if (imageLoader) {
+ imageLoader->RemoveNativeObserver(mListener);
+ }
+ reinterpret_cast<SVGImageListener*>(mListener.get())->SetFrame(nullptr);
+ }
+ mListener = nullptr;
+}
+
+void SVGImageFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::image),
+ "Content is not an SVG image!");
+
+ SVGGeometryFrame::Init(aContent, aParent, aPrevInFlow);
+
+ if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ // Non-display frames are likely to be patterns, masks or the like.
+ // Treat them as always visible.
+ // This call must happen before the FrameCreated. This is because the
+ // primary frame pointer on our content node isn't set until after this
+ // function ends, so there is no way for the resulting OnVisibilityChange
+ // notification to get a frame. FrameCreated has a workaround for this in
+ // that it passes our frame around so it can be accessed. OnVisibilityChange
+ // doesn't have that workaround.
+ IncApproximateVisibleCount();
+ }
+
+ mListener = new SVGImageListener(this);
+ nsCOMPtr<nsIImageLoadingContent> imageLoader =
+ do_QueryInterface(GetContent());
+ if (!imageLoader) {
+ MOZ_CRASH("Why is this not an image loading content?");
+ }
+
+ // We should have a PresContext now, so let's notify our image loader that
+ // we need to register any image animations with the refresh driver.
+ imageLoader->FrameCreated(this);
+
+ imageLoader->AddNativeObserver(mListener);
+}
+
+/* virtual */
+void SVGImageFrame::DestroyFrom(nsIFrame* aDestructRoot,
+ PostDestroyData& aPostDestroyData) {
+ if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ DecApproximateVisibleCount();
+ }
+
+ if (mReflowCallbackPosted) {
+ PresShell()->CancelReflowCallback(this);
+ mReflowCallbackPosted = false;
+ }
+
+ nsCOMPtr<nsIImageLoadingContent> imageLoader =
+ do_QueryInterface(nsIFrame::mContent);
+
+ if (imageLoader) {
+ imageLoader->FrameDestroyed(this);
+ }
+
+ nsIFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
+}
+
+/* virtual */
+void SVGImageFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
+ SVGGeometryFrame::DidSetComputedStyle(aOldStyle);
+
+ if (!mImageContainer || !aOldStyle) {
+ return;
+ }
+
+ nsCOMPtr<imgIRequest> currentRequest;
+ nsCOMPtr<nsIImageLoadingContent> imageLoader =
+ do_QueryInterface(GetContent());
+ if (imageLoader) {
+ imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(currentRequest));
+ }
+
+ StyleImageOrientation newOrientation =
+ StyleVisibility()->UsedImageOrientation(currentRequest);
+ StyleImageOrientation oldOrientation =
+ aOldStyle->StyleVisibility()->UsedImageOrientation(currentRequest);
+
+ if (oldOrientation != newOrientation) {
+ nsCOMPtr<imgIContainer> image(mImageContainer->Unwrap());
+ mImageContainer = nsLayoutUtils::OrientImage(image, newOrientation);
+ }
+
+ // TODO(heycam): We should handle aspect-ratio, like nsImageFrame does.
+}
+
+//----------------------------------------------------------------------
+// nsIFrame methods:
+
+nsresult SVGImageFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType) {
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::preserveAspectRatio) {
+ // We don't paint the content of the image using display lists, therefore
+ // we have to invalidate for this children-only transform changes since
+ // there is no layer tree to notice that the transform changed and
+ // recomposite.
+ InvalidateFrame();
+ return NS_OK;
+ }
+ }
+
+ // Currently our SMIL implementation does not modify the DOM attributes. Once
+ // we implement the SVG 2 SMIL behaviour this can be removed
+ // SVGImageElement::AfterSetAttr's implementation will be sufficient.
+ if (aModType == MutationEvent_Binding::SMIL &&
+ aAttribute == nsGkAtoms::href &&
+ (aNameSpaceID == kNameSpaceID_XLink ||
+ aNameSpaceID == kNameSpaceID_None)) {
+ SVGImageElement* element = static_cast<SVGImageElement*>(GetContent());
+
+ bool hrefIsSet =
+ element->mStringAttributes[SVGImageElement::HREF].IsExplicitlySet() ||
+ element->mStringAttributes[SVGImageElement::XLINK_HREF]
+ .IsExplicitlySet();
+ if (hrefIsSet) {
+ element->LoadSVGImage(true, true);
+ } else {
+ element->CancelImageRequests(true);
+ }
+ }
+
+ return SVGGeometryFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+void SVGImageFrame::OnVisibilityChange(
+ Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
+ nsCOMPtr<nsIImageLoadingContent> imageLoader =
+ do_QueryInterface(GetContent());
+ if (!imageLoader) {
+ SVGGeometryFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+ return;
+ }
+
+ imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+
+ SVGGeometryFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+}
+
+gfx::Matrix SVGImageFrame::GetRasterImageTransform(int32_t aNativeWidth,
+ int32_t aNativeHeight) {
+ float x, y, width, height;
+ SVGImageElement* element = static_cast<SVGImageElement*>(GetContent());
+ SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
+ element, &x, &y, &width, &height);
+
+ Matrix viewBoxTM = SVGContentUtils::GetViewBoxTransform(
+ width, height, 0, 0, aNativeWidth, aNativeHeight,
+ element->mPreserveAspectRatio);
+
+ return viewBoxTM * gfx::Matrix::Translation(x, y);
+}
+
+gfx::Matrix SVGImageFrame::GetVectorImageTransform() {
+ float x, y;
+ SVGImageElement* element = static_cast<SVGImageElement*>(GetContent());
+ SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y>(element, &x, &y);
+
+ // No viewBoxTM needed here -- our height/width overrides any concept of
+ // "native size" that the SVG image has, and it will handle viewBox and
+ // preserveAspectRatio on its own once we give it a region to draw into.
+
+ return gfx::Matrix::Translation(x, y);
+}
+
+bool SVGImageFrame::GetIntrinsicImageDimensions(
+ mozilla::gfx::Size& aSize, mozilla::AspectRatio& aAspectRatio) const {
+ if (!mImageContainer) {
+ return false;
+ }
+
+ ImageResolution resolution = mImageContainer->GetResolution();
+
+ int32_t width, height;
+ if (NS_FAILED(mImageContainer->GetWidth(&width))) {
+ aSize.width = -1;
+ } else {
+ aSize.width = width;
+ resolution.ApplyXTo(aSize.width);
+ }
+
+ if (NS_FAILED(mImageContainer->GetHeight(&height))) {
+ aSize.height = -1;
+ } else {
+ aSize.height = height;
+ resolution.ApplyYTo(aSize.height);
+ }
+
+ Maybe<AspectRatio> asp = mImageContainer->GetIntrinsicRatio();
+ aAspectRatio = asp.valueOr(AspectRatio{});
+
+ return true;
+}
+
+bool SVGImageFrame::TransformContextForPainting(gfxContext* aGfxContext,
+ const gfxMatrix& aTransform) {
+ gfx::Matrix imageTransform;
+ if (mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
+ imageTransform = GetVectorImageTransform() * ToMatrix(aTransform);
+ } else {
+ int32_t nativeWidth, nativeHeight;
+ if (NS_FAILED(mImageContainer->GetWidth(&nativeWidth)) ||
+ NS_FAILED(mImageContainer->GetHeight(&nativeHeight)) ||
+ nativeWidth == 0 || nativeHeight == 0) {
+ return false;
+ }
+ mImageContainer->GetResolution().ApplyTo(nativeWidth, nativeHeight);
+ imageTransform = GetRasterImageTransform(nativeWidth, nativeHeight) *
+ ToMatrix(aTransform);
+
+ // NOTE: We need to cancel out the effects of Full-Page-Zoom, or else
+ // it'll get applied an extra time by DrawSingleUnscaledImage.
+ nscoord appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
+ gfxFloat pageZoomFactor =
+ nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPx);
+ imageTransform.PreScale(pageZoomFactor, pageZoomFactor);
+ }
+
+ if (imageTransform.IsSingular()) {
+ return false;
+ }
+
+ aGfxContext->Multiply(ThebesMatrix(imageTransform));
+ return true;
+}
+
+//----------------------------------------------------------------------
+// ISVGDisplayableFrame methods:
+void SVGImageFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect) {
+ if (!StyleVisibility()->IsVisible()) {
+ return;
+ }
+
+ float x, y, width, height;
+ SVGImageElement* imgElem = static_cast<SVGImageElement*>(GetContent());
+ SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
+ imgElem, &x, &y, &width, &height);
+ NS_ASSERTION(width > 0 && height > 0,
+ "Should only be painting things with valid width/height");
+
+ if (!mImageContainer) {
+ nsCOMPtr<imgIRequest> currentRequest;
+ nsCOMPtr<nsIImageLoadingContent> imageLoader =
+ do_QueryInterface(GetContent());
+ if (imageLoader)
+ imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(currentRequest));
+
+ if (currentRequest)
+ currentRequest->GetImage(getter_AddRefs(mImageContainer));
+ }
+
+ if (mImageContainer) {
+ gfxContextAutoSaveRestore autoRestorer(&aContext);
+
+ if (StyleDisplay()->IsScrollableOverflow()) {
+ gfxRect clipRect =
+ SVGUtils::GetClipRectForFrame(this, x, y, width, height);
+ SVGUtils::SetClipRect(&aContext, aTransform, clipRect);
+ }
+
+ if (!TransformContextForPainting(&aContext, aTransform)) {
+ return;
+ }
+
+ // fill-opacity doesn't affect <image>, so if we're allowed to
+ // optimize group opacity, the opacity used for compositing the
+ // image into the current canvas is just the group opacity.
+ float opacity = 1.0f;
+ if (SVGUtils::CanOptimizeOpacity(this)) {
+ opacity = StyleEffects()->mOpacity;
+ }
+
+ if (opacity != 1.0f ||
+ StyleEffects()->mMixBlendMode != StyleBlend::Normal) {
+ aContext.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity);
+ }
+
+ nscoord appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
+ nsRect dirtyRect; // only used if aDirtyRect is non-null
+ if (aDirtyRect) {
+ NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "Display lists handle dirty rect intersection test");
+ dirtyRect = ToAppUnits(*aDirtyRect, appUnitsPerDevPx);
+
+ // dirtyRect is relative to the outer <svg>, we should transform it
+ // down to <image>.
+ Rect dir(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height);
+ dir.Scale(1.f / AppUnitsPerCSSPixel());
+
+ // FIXME: This isn't correct if there is an inner <svg> enclosing
+ // the <image>. But that seems to be a quite obscure usecase, we can
+ // add a dedicated utility for that purpose to replace the GetCTM
+ // here if necessary.
+ auto mat = SVGContentUtils::GetCTM(
+ static_cast<SVGImageElement*>(GetContent()), false);
+ if (mat.IsSingular()) {
+ return;
+ }
+
+ mat.Invert();
+ dir = mat.TransformRect(dir);
+
+ // x, y offset of <image> is not included in CTM.
+ dir.MoveBy(-x, -y);
+
+ dir.Scale(AppUnitsPerCSSPixel());
+ dir.Round();
+ dirtyRect = nsRect(dir.x, dir.y, dir.width, dir.height);
+ }
+
+ uint32_t flags = aImgParams.imageFlags;
+ if (mForceSyncDecoding) {
+ flags |= imgIContainer::FLAG_SYNC_DECODE;
+ }
+
+ if (mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
+ // Package up the attributes of this image element which can override the
+ // attributes of mImageContainer's internal SVG document. The 'width' &
+ // 'height' values we're passing in here are in CSS units (though they
+ // come from width/height *attributes* in SVG). They influence the region
+ // of the SVG image's internal document that is visible, in combination
+ // with preserveAspectRatio and viewBox.
+ const SVGImageContext context(
+ Some(CSSIntSize::Truncate(width, height)),
+ Some(imgElem->mPreserveAspectRatio.GetAnimValue()));
+
+ // For the actual draw operation to draw crisply (and at the right size),
+ // our destination rect needs to be |width|x|height|, *in dev pixels*.
+ LayoutDeviceSize devPxSize(width, height);
+ nsRect destRect(nsPoint(), LayoutDevicePixel::ToAppUnits(
+ devPxSize, appUnitsPerDevPx));
+
+ // Note: Can't use DrawSingleUnscaledImage for the TYPE_VECTOR case.
+ // That method needs our image to have a fixed native width & height,
+ // and that's not always true for TYPE_VECTOR images.
+ aImgParams.result &= nsLayoutUtils::DrawSingleImage(
+ aContext, PresContext(), mImageContainer,
+ nsLayoutUtils::GetSamplingFilterForFrame(this), destRect,
+ aDirtyRect ? dirtyRect : destRect, context, flags);
+ } else { // mImageContainer->GetType() == TYPE_RASTER
+ aImgParams.result &= nsLayoutUtils::DrawSingleUnscaledImage(
+ aContext, PresContext(), mImageContainer,
+ nsLayoutUtils::GetSamplingFilterForFrame(this), nsPoint(0, 0),
+ aDirtyRect ? &dirtyRect : nullptr, SVGImageContext(), flags);
+ }
+
+ if (opacity != 1.0f ||
+ StyleEffects()->mMixBlendMode != StyleBlend::Normal) {
+ aContext.PopGroupAndBlend();
+ }
+ // gfxContextAutoSaveRestore goes out of scope & cleans up our gfxContext
+ }
+}
+
+bool SVGImageFrame::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder, DisplaySVGGeometry* aItem,
+ bool aDryRun) {
+ if (!StyleVisibility()->IsVisible()) {
+ return true;
+ }
+
+ float opacity = 1.0f;
+ if (SVGUtils::CanOptimizeOpacity(this)) {
+ opacity = StyleEffects()->mOpacity;
+ }
+
+ if (opacity != 1.0f) {
+ // FIXME: not implemented, might be trivial
+ return false;
+ }
+ if (StyleEffects()->mMixBlendMode != StyleBlend::Normal) {
+ // FIXME: not implemented
+ return false;
+ }
+
+ // try to setup the image
+ if (!mImageContainer) {
+ nsCOMPtr<imgIRequest> currentRequest;
+ nsCOMPtr<nsIImageLoadingContent> imageLoader =
+ do_QueryInterface(GetContent());
+ if (imageLoader) {
+ imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(currentRequest));
+ }
+
+ if (currentRequest) {
+ currentRequest->GetImage(getter_AddRefs(mImageContainer));
+ }
+ }
+
+ if (!mImageContainer) {
+ // nothing to draw (yet)
+ return true;
+ }
+
+ uint32_t flags = aDisplayListBuilder->GetImageDecodeFlags();
+ if (mForceSyncDecoding) {
+ flags |= imgIContainer::FLAG_SYNC_DECODE;
+ }
+
+ // Compute bounds of the image
+ nscoord appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
+ int32_t appUnitsPerCSSPixel = AppUnitsPerCSSPixel();
+
+ float x, y, width, height;
+ SVGImageElement* imgElem = static_cast<SVGImageElement*>(GetContent());
+ SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
+ imgElem, &x, &y, &width, &height);
+ NS_ASSERTION(width > 0 && height > 0,
+ "Should only be painting things with valid width/height");
+
+ auto toReferenceFrame = aItem->ToReferenceFrame();
+ auto appRect = nsLayoutUtils::RoundGfxRectToAppRect(Rect(0, 0, width, height),
+ appUnitsPerCSSPixel);
+ appRect += toReferenceFrame;
+ auto destRect = LayoutDeviceRect::FromAppUnits(appRect, appUnitsPerDevPx);
+ auto clipRect = destRect;
+
+ if (StyleDisplay()->IsScrollableOverflow()) {
+ // Apply potential non-trivial clip
+ auto cssClip = SVGUtils::GetClipRectForFrame(this, 0, 0, width, height);
+ auto appClip =
+ nsLayoutUtils::RoundGfxRectToAppRect(cssClip, appUnitsPerCSSPixel);
+ appClip += toReferenceFrame;
+ clipRect = LayoutDeviceRect::FromAppUnits(appClip, appUnitsPerDevPx);
+
+ // Apply preserveAspectRatio
+ if (mImageContainer->GetType() == imgIContainer::TYPE_RASTER) {
+ int32_t nativeWidth, nativeHeight;
+ if (NS_FAILED(mImageContainer->GetWidth(&nativeWidth)) ||
+ NS_FAILED(mImageContainer->GetHeight(&nativeHeight)) ||
+ nativeWidth == 0 || nativeHeight == 0) {
+ // Image has no size; nothing to draw
+ return true;
+ }
+
+ mImageContainer->GetResolution().ApplyTo(nativeWidth, nativeHeight);
+
+ auto preserveAspectRatio = imgElem->mPreserveAspectRatio.GetAnimValue();
+ uint16_t align = preserveAspectRatio.GetAlign();
+ uint16_t meetOrSlice = preserveAspectRatio.GetMeetOrSlice();
+
+ // default to the defaults
+ if (align == SVG_PRESERVEASPECTRATIO_UNKNOWN) {
+ align = SVG_PRESERVEASPECTRATIO_XMIDYMID;
+ }
+ if (meetOrSlice == SVG_MEETORSLICE_UNKNOWN) {
+ meetOrSlice = SVG_MEETORSLICE_MEET;
+ }
+
+ // aspect > 1 is horizontal
+ // aspect < 1 is vertical
+ float nativeAspect = ((float)nativeWidth) / ((float)nativeHeight);
+ float viewAspect = width / height;
+
+ // "Meet" is "fit image to view"; "Slice" is "cover view with image".
+ //
+ // Whether we meet or slice, one side of the destRect will always be
+ // perfectly spanned by our image. The only questions to answer are
+ // "which side won't span perfectly" and "should that side be grown
+ // or shrunk".
+ //
+ // Because we fit our image to the destRect, this all just reduces to:
+ // "if meet, shrink to fit. if slice, grow to fit."
+ if (align != SVG_PRESERVEASPECTRATIO_NONE && nativeAspect != viewAspect) {
+ // Slightly redundant bools, but they make the conditions clearer
+ bool tooTall = nativeAspect > viewAspect;
+ bool tooWide = nativeAspect < viewAspect;
+ if ((meetOrSlice == SVG_MEETORSLICE_MEET && tooTall) ||
+ (meetOrSlice == SVG_MEETORSLICE_SLICE && tooWide)) {
+ // Adjust height and realign y
+ auto oldHeight = destRect.height;
+ destRect.height = destRect.width / nativeAspect;
+ auto heightChange = oldHeight - destRect.height;
+ switch (align) {
+ case SVG_PRESERVEASPECTRATIO_XMINYMIN:
+ case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
+ case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
+ // align to top (no-op)
+ break;
+ case SVG_PRESERVEASPECTRATIO_XMINYMID:
+ case SVG_PRESERVEASPECTRATIO_XMIDYMID:
+ case SVG_PRESERVEASPECTRATIO_XMAXYMID:
+ // align to center
+ destRect.y += heightChange / 2.0f;
+ break;
+ case SVG_PRESERVEASPECTRATIO_XMINYMAX:
+ case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
+ case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
+ // align to bottom
+ destRect.y += heightChange;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown value for align");
+ }
+ } else if ((meetOrSlice == SVG_MEETORSLICE_MEET && tooWide) ||
+ (meetOrSlice == SVG_MEETORSLICE_SLICE && tooTall)) {
+ // Adjust width and realign x
+ auto oldWidth = destRect.width;
+ destRect.width = destRect.height * nativeAspect;
+ auto widthChange = oldWidth - destRect.width;
+ switch (align) {
+ case SVG_PRESERVEASPECTRATIO_XMINYMIN:
+ case SVG_PRESERVEASPECTRATIO_XMINYMID:
+ case SVG_PRESERVEASPECTRATIO_XMINYMAX:
+ // align to left (no-op)
+ break;
+ case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
+ case SVG_PRESERVEASPECTRATIO_XMIDYMID:
+ case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
+ // align to center
+ destRect.x += widthChange / 2.0f;
+ break;
+ case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
+ case SVG_PRESERVEASPECTRATIO_XMAXYMID:
+ case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
+ // align to right
+ destRect.x += widthChange;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown value for align");
+ }
+ }
+ }
+ }
+ }
+
+ SVGImageContext svgContext;
+ if (mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
+ if (StaticPrefs::image_svg_blob_image()) {
+ flags |= imgIContainer::FLAG_RECORD_BLOB;
+ }
+ // Forward preserveAspectRatio to inner SVGs
+ svgContext.SetViewportSize(Some(CSSIntSize::Truncate(width, height)));
+ svgContext.SetPreserveAspectRatio(
+ Some(imgElem->mPreserveAspectRatio.GetAnimValue()));
+ }
+
+ Maybe<ImageIntRegion> region;
+ IntSize decodeSize = nsLayoutUtils::ComputeImageContainerDrawingParameters(
+ mImageContainer, this, destRect, clipRect, aSc, flags, svgContext,
+ region);
+
+ RefPtr<image::WebRenderImageProvider> provider;
+ ImgDrawResult drawResult = mImageContainer->GetImageProvider(
+ aManager->LayerManager(), decodeSize, svgContext, region, flags,
+ getter_AddRefs(provider));
+
+ // While we got a container, it may not contain a fully decoded surface. If
+ // that is the case, and we have an image we were previously displaying which
+ // has a fully decoded surface, then we should prefer the previous image.
+ switch (drawResult) {
+ case ImgDrawResult::NOT_READY:
+ case ImgDrawResult::TEMPORARY_ERROR:
+ // nothing to draw (yet)
+ return true;
+ case ImgDrawResult::NOT_SUPPORTED:
+ // things we haven't implemented for WR yet
+ return false;
+ default:
+ // image is ready to draw
+ break;
+ }
+
+ // Don't do any actual mutations to state if we're doing a dry run
+ // (used to decide if we're making this into an active layer)
+ if (!aDryRun) {
+ // If the image container is empty, we don't want to fallback. Any other
+ // failure will be due to resource constraints and fallback is unlikely to
+ // help us. Hence we can ignore the return value from PushImage.
+ if (provider) {
+ aManager->CommandBuilder().PushImageProvider(aItem, provider, drawResult,
+ aBuilder, aResources,
+ destRect, clipRect);
+ }
+ }
+
+ return true;
+}
+
+nsIFrame* SVGImageFrame::GetFrameForPoint(const gfxPoint& aPoint) {
+ if (!HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) && !GetHitTestFlags()) {
+ return nullptr;
+ }
+
+ Rect rect;
+ SVGImageElement* element = static_cast<SVGImageElement*>(GetContent());
+ SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
+ element, &rect.x, &rect.y, &rect.width, &rect.height);
+
+ if (!rect.Contains(ToPoint(aPoint))) {
+ return nullptr;
+ }
+
+ // Special case for raster images -- we only want to accept points that fall
+ // in the underlying image's (scaled to fit) native bounds. That region
+ // doesn't necessarily map to our <image> element's [x,y,width,height] if the
+ // raster image's aspect ratio is being preserved. We have to look up the
+ // native image size & our viewBox transform in order to filter out points
+ // that fall outside that area. (This special case doesn't apply to vector
+ // images because they don't limit their drawing to explicit "native
+ // bounds" -- they have an infinite canvas on which to place content.)
+ if (StyleDisplay()->IsScrollableOverflow() && mImageContainer) {
+ if (mImageContainer->GetType() == imgIContainer::TYPE_RASTER) {
+ int32_t nativeWidth, nativeHeight;
+ if (NS_FAILED(mImageContainer->GetWidth(&nativeWidth)) ||
+ NS_FAILED(mImageContainer->GetHeight(&nativeHeight)) ||
+ nativeWidth == 0 || nativeHeight == 0) {
+ return nullptr;
+ }
+ mImageContainer->GetResolution().ApplyTo(nativeWidth, nativeHeight);
+ Matrix viewBoxTM = SVGContentUtils::GetViewBoxTransform(
+ rect.width, rect.height, 0, 0, nativeWidth, nativeHeight,
+ element->mPreserveAspectRatio);
+ if (!SVGUtils::HitTestRect(viewBoxTM, 0, 0, nativeWidth, nativeHeight,
+ aPoint.x - rect.x, aPoint.y - rect.y)) {
+ return nullptr;
+ }
+ }
+ }
+
+ return this;
+}
+
+//----------------------------------------------------------------------
+// SVGGeometryFrame methods:
+
+// Lie about our fill/stroke so that covered region and hit detection work
+// properly
+
+void SVGImageFrame::ReflowSVG() {
+ NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
+ "This call is probably a wasteful mistake");
+
+ MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
+ "ReflowSVG mechanism not designed for this");
+
+ if (!SVGUtils::NeedsReflowSVG(this)) {
+ return;
+ }
+
+ float x, y, width, height;
+ SVGImageElement* element = static_cast<SVGImageElement*>(GetContent());
+ SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
+ element, &x, &y, &width, &height);
+
+ Rect extent(x, y, width, height);
+
+ if (!extent.IsEmpty()) {
+ mRect = nsLayoutUtils::RoundGfxRectToAppRect(extent, AppUnitsPerCSSPixel());
+ } else {
+ mRect.SetEmpty();
+ }
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ // Make sure we have our filter property (if any) before calling
+ // FinishAndStoreOverflow (subsequent filter changes are handled off
+ // nsChangeHint_UpdateEffects):
+ SVGObserverUtils::UpdateEffects(this);
+
+ if (!mReflowCallbackPosted) {
+ mReflowCallbackPosted = true;
+ PresShell()->PostReflowCallback(this);
+ }
+ }
+
+ nsRect overflow = nsRect(nsPoint(0, 0), mRect.Size());
+ OverflowAreas overflowAreas(overflow, overflow);
+ FinishAndStoreOverflow(overflowAreas, mRect.Size());
+
+ RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ // Invalidate, but only if this is not our first reflow (since if it is our
+ // first reflow then we haven't had our first paint yet).
+ if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ InvalidateFrame();
+ }
+}
+
+bool SVGImageFrame::ReflowFinished() {
+ mReflowCallbackPosted = false;
+
+ // XXX(seth): We don't need this. The purpose of updating visibility
+ // synchronously is to ensure that animated images start animating
+ // immediately. In the short term, however,
+ // nsImageLoadingContent::OnUnlockedDraw() is enough to ensure that
+ // animations start as soon as the image is painted for the first time, and in
+ // the long term we want to update visibility information from the display
+ // list whenever we paint, so we don't actually need to do this. However, to
+ // avoid behavior changes during the transition from the old image visibility
+ // code, we'll leave it in for now.
+ UpdateVisibilitySynchronously();
+
+ return false;
+}
+
+void SVGImageFrame::ReflowCallbackCanceled() { mReflowCallbackPosted = false; }
+
+uint16_t SVGImageFrame::GetHitTestFlags() {
+ uint16_t flags = 0;
+
+ switch (Style()->PointerEvents()) {
+ case StylePointerEvents::None:
+ break;
+ case StylePointerEvents::Visiblepainted:
+ case StylePointerEvents::Auto:
+ if (StyleVisibility()->IsVisible()) {
+ /* XXX: should check pixel transparency */
+ flags |= SVG_HIT_TEST_FILL;
+ }
+ break;
+ case StylePointerEvents::Visiblefill:
+ case StylePointerEvents::Visiblestroke:
+ case StylePointerEvents::Visible:
+ if (StyleVisibility()->IsVisible()) {
+ flags |= SVG_HIT_TEST_FILL;
+ }
+ break;
+ case StylePointerEvents::Painted:
+ /* XXX: should check pixel transparency */
+ flags |= SVG_HIT_TEST_FILL;
+ break;
+ case StylePointerEvents::Fill:
+ case StylePointerEvents::Stroke:
+ case StylePointerEvents::All:
+ flags |= SVG_HIT_TEST_FILL;
+ break;
+ default:
+ NS_ERROR("not reached");
+ break;
+ }
+
+ return flags;
+}
+
+//----------------------------------------------------------------------
+// SVGImageListener implementation
+
+NS_IMPL_ISUPPORTS(SVGImageListener, imgINotificationObserver)
+
+SVGImageListener::SVGImageListener(SVGImageFrame* aFrame) : mFrame(aFrame) {}
+
+void SVGImageListener::Notify(imgIRequest* aRequest, int32_t aType,
+ const nsIntRect* aData) {
+ if (!mFrame) {
+ return;
+ }
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ mFrame->InvalidateFrame();
+ nsLayoutUtils::PostRestyleEvent(mFrame->GetContent()->AsElement(),
+ RestyleHint{0},
+ nsChangeHint_InvalidateRenderingObservers);
+ SVGUtils::ScheduleReflowSVG(mFrame);
+ }
+
+ if (aType == imgINotificationObserver::FRAME_UPDATE) {
+ // No new dimensions, so we don't need to call
+ // SVGUtils::InvalidateAndScheduleBoundsUpdate.
+ nsLayoutUtils::PostRestyleEvent(mFrame->GetContent()->AsElement(),
+ RestyleHint{0},
+ nsChangeHint_InvalidateRenderingObservers);
+ mFrame->InvalidateFrame();
+ }
+
+ if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
+ // Called once the resource's dimensions have been obtained.
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ if (image) {
+ StyleImageOrientation orientation =
+ mFrame->StyleVisibility()->UsedImageOrientation(aRequest);
+ image = nsLayoutUtils::OrientImage(image, orientation);
+ image->SetAnimationMode(mFrame->PresContext()->ImageAnimationMode());
+ mFrame->mImageContainer = std::move(image);
+ }
+ mFrame->InvalidateFrame();
+ nsLayoutUtils::PostRestyleEvent(mFrame->GetContent()->AsElement(),
+ RestyleHint{0},
+ nsChangeHint_InvalidateRenderingObservers);
+ SVGUtils::ScheduleReflowSVG(mFrame);
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGImageFrame.h b/layout/svg/SVGImageFrame.h
new file mode 100644
index 0000000000..372f339778
--- /dev/null
+++ b/layout/svg/SVGImageFrame.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 LAYOUT_SVG_SVGIMAGEFRAME_H_
+#define LAYOUT_SVG_SVGIMAGEFRAME_H_
+
+// Keep in (case-insensitive) order:
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "mozilla/gfx/2D.h"
+#include "imgIContainer.h"
+#include "nsContainerFrame.h"
+#include "imgINotificationObserver.h"
+#include "mozilla/SVGGeometryFrame.h"
+#include "nsIReflowCallback.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGImageFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGImageFrame final : public SVGGeometryFrame, public nsIReflowCallback {
+ friend nsIFrame* ::NS_NewSVGImageFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ bool CreateWebRenderCommands(wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ const layers::StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder,
+ DisplaySVGGeometry* aItem,
+ bool aDryRun) override;
+
+ protected:
+ explicit SVGImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : SVGGeometryFrame(aStyle, aPresContext, kClassID),
+ mReflowCallbackPosted(false),
+ mForceSyncDecoding(false) {
+ EnableVisibilityTracking();
+ }
+
+ virtual ~SVGImageFrame();
+
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(SVGImageFrame)
+
+ // ISVGDisplayableFrame interface:
+ void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect = nullptr) override;
+ nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
+ void ReflowSVG() override;
+
+ // SVGGeometryFrame methods:
+ uint16_t GetHitTestFlags() override;
+
+ // nsIFrame interface:
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ void OnVisibilityChange(
+ Visibility aNewVisibility,
+ const Maybe<OnNonvisible>& aNonvisibleAction = Nothing()) override;
+
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+ void DestroyFrom(nsIFrame* aDestructRoot,
+ PostDestroyData& aPostDestroyData) override;
+ void DidSetComputedStyle(ComputedStyle* aOldStyle) final;
+
+ bool GetIntrinsicImageDimensions(gfx::Size& aSize,
+ AspectRatio& aAspectRatio) const;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGImage"_ns, aResult);
+ }
+#endif
+
+ // nsIReflowCallback
+ bool ReflowFinished() override;
+ void ReflowCallbackCanceled() override;
+
+ /// Always sync decode our image when painting if @aForce is true.
+ void SetForceSyncDecoding(bool aForce) { mForceSyncDecoding = aForce; }
+
+ private:
+ gfx::Matrix GetRasterImageTransform(int32_t aNativeWidth,
+ int32_t aNativeHeight);
+ gfx::Matrix GetVectorImageTransform();
+ bool TransformContextForPainting(gfxContext* aGfxContext,
+ const gfxMatrix& aTransform);
+
+ nsCOMPtr<imgINotificationObserver> mListener;
+
+ nsCOMPtr<imgIContainer> mImageContainer;
+
+ bool mReflowCallbackPosted;
+ bool mForceSyncDecoding;
+
+ friend class SVGImageListener;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGIMAGEFRAME_H_
diff --git a/layout/svg/SVGInnerSVGFrame.cpp b/layout/svg/SVGInnerSVGFrame.cpp
new file mode 100644
index 0000000000..0a547ccc94
--- /dev/null
+++ b/layout/svg/SVGInnerSVGFrame.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/. */
+
+// Main header first:
+#include "SVGInnerSVGFrame.h"
+
+#include "mozilla/PresShell.h"
+
+nsIFrame* NS_NewSVGInnerSVGFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGInnerSVGFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGInnerSVGFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods
+
+NS_QUERYFRAME_HEAD(SVGInnerSVGFrame)
+ NS_QUERYFRAME_ENTRY(SVGInnerSVGFrame)
+ NS_QUERYFRAME_ENTRY(ISVGSVGFrame)
+NS_QUERYFRAME_TAIL_INHERITING(SVGViewportFrame)
+
+#ifdef DEBUG
+void SVGInnerSVGFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svg),
+ "Content is not an SVG 'svg' element!");
+
+ SVGViewportFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+} // namespace mozilla
diff --git a/layout/svg/SVGInnerSVGFrame.h b/layout/svg/SVGInnerSVGFrame.h
new file mode 100644
index 0000000000..1db386f344
--- /dev/null
+++ b/layout/svg/SVGInnerSVGFrame.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 LAYOUT_SVG_SVGINNERSVGFRAME_H_
+#define LAYOUT_SVG_SVGINNERSVGFRAME_H_
+
+#include "SVGViewportFrame.h"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGInnerSVGFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGInnerSVGFrame final : public SVGViewportFrame {
+ friend nsIFrame* ::NS_NewSVGInnerSVGFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ protected:
+ explicit SVGInnerSVGFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : SVGViewportFrame(aStyle, aPresContext, kClassID) {}
+
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(SVGInnerSVGFrame)
+
+#ifdef DEBUG
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGInnerSVG"_ns, aResult);
+ }
+#endif
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGINNERSVGFRAME_H_
diff --git a/layout/svg/SVGIntegrationUtils.cpp b/layout/svg/SVGIntegrationUtils.cpp
new file mode 100644
index 0000000000..7e4a29c424
--- /dev/null
+++ b/layout/svg/SVGIntegrationUtils.cpp
@@ -0,0 +1,1256 @@
+/* -*- 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:
+#include "SVGIntegrationUtils.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfxDrawable.h"
+
+#include "nsCSSAnonBoxes.h"
+#include "nsCSSRendering.h"
+#include "nsDisplayList.h"
+#include "nsLayoutUtils.h"
+#include "gfxContext.h"
+#include "SVGPaintServerFrame.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/CSSClipPathInstance.h"
+#include "mozilla/FilterInstance.h"
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/SVGClipPathFrame.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGMaskFrame.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/SVGElement.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::layers;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+namespace mozilla {
+
+/**
+ * This class is used to get the pre-effects ink overflow rect of a frame,
+ * or, in the case of a frame with continuations, to collect the union of the
+ * pre-effects ink overflow rects of all the continuations. The result is
+ * relative to the origin (top left corner of the border box) of the frame, or,
+ * if the frame has continuations, the origin of the _first_ continuation.
+ */
+class PreEffectsInkOverflowCollector : public nsLayoutUtils::BoxCallback {
+ public:
+ /**
+ * If the pre-effects ink overflow rect of the frame being examined
+ * happens to be known, it can be passed in as aCurrentFrame and its
+ * pre-effects ink overflow rect can be passed in as
+ * aCurrentFrameOverflowArea. This is just an optimization to save a
+ * frame property lookup - these arguments are optional.
+ */
+ PreEffectsInkOverflowCollector(nsIFrame* aFirstContinuation,
+ nsIFrame* aCurrentFrame,
+ const nsRect& aCurrentFrameOverflowArea,
+ bool aInReflow)
+ : mFirstContinuation(aFirstContinuation),
+ mCurrentFrame(aCurrentFrame),
+ mCurrentFrameOverflowArea(aCurrentFrameOverflowArea),
+ mInReflow(aInReflow) {
+ NS_ASSERTION(!mFirstContinuation->GetPrevContinuation(),
+ "We want the first continuation here");
+ }
+
+ void AddBox(nsIFrame* aFrame) override {
+ nsRect overflow = (aFrame == mCurrentFrame)
+ ? mCurrentFrameOverflowArea
+ : PreEffectsInkOverflowRect(aFrame, mInReflow);
+ mResult.UnionRect(mResult,
+ overflow + aFrame->GetOffsetTo(mFirstContinuation));
+ }
+
+ nsRect GetResult() const { return mResult; }
+
+ private:
+ static nsRect PreEffectsInkOverflowRect(nsIFrame* aFrame, bool aInReflow) {
+ nsRect* r = aFrame->GetProperty(nsIFrame::PreEffectsBBoxProperty());
+ if (r) {
+ return *r;
+ }
+
+#ifdef DEBUG
+ // Having PreTransformOverflowAreasProperty cached means
+ // InkOverflowRect() will return post-effect rect, which is not what
+ // we want. This function intentional reports pre-effect rect. But it does
+ // not matter if there is no SVG effect on this frame, since no effect
+ // means post-effect rect matches pre-effect rect.
+ //
+ // This function may be called during reflow or painting. We should only
+ // do this check in painting process since the PreEffectsBBoxProperty of
+ // continuations are not set correctly while reflowing.
+ if (SVGIntegrationUtils::UsingOverflowAffectingEffects(aFrame) &&
+ !aInReflow) {
+ OverflowAreas* preTransformOverflows =
+ aFrame->GetProperty(nsIFrame::PreTransformOverflowAreasProperty());
+
+ MOZ_ASSERT(!preTransformOverflows,
+ "InkOverflowRect() won't return the pre-effects rect!");
+ }
+#endif
+ return aFrame->InkOverflowRectRelativeToSelf();
+ }
+
+ nsIFrame* mFirstContinuation;
+ nsIFrame* mCurrentFrame;
+ const nsRect& mCurrentFrameOverflowArea;
+ nsRect mResult;
+ bool mInReflow;
+};
+
+/**
+ * Gets the union of the pre-effects ink overflow rects of all of a frame's
+ * continuations, in "user space".
+ */
+static nsRect GetPreEffectsInkOverflowUnion(
+ nsIFrame* aFirstContinuation, nsIFrame* aCurrentFrame,
+ const nsRect& aCurrentFramePreEffectsOverflow,
+ const nsPoint& aFirstContinuationToUserSpace, bool aInReflow) {
+ NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(),
+ "Need first continuation here");
+ PreEffectsInkOverflowCollector collector(aFirstContinuation, aCurrentFrame,
+ aCurrentFramePreEffectsOverflow,
+ aInReflow);
+ // Compute union of all overflow areas relative to aFirstContinuation:
+ nsLayoutUtils::GetAllInFlowBoxes(aFirstContinuation, &collector);
+ // Return the result in user space:
+ return collector.GetResult() + aFirstContinuationToUserSpace;
+}
+
+/**
+ * Gets the pre-effects ink overflow rect of aCurrentFrame in "user space".
+ */
+static nsRect GetPreEffectsInkOverflow(
+ nsIFrame* aFirstContinuation, nsIFrame* aCurrentFrame,
+ const nsPoint& aFirstContinuationToUserSpace) {
+ NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(),
+ "Need first continuation here");
+ PreEffectsInkOverflowCollector collector(aFirstContinuation, nullptr,
+ nsRect(), false);
+ // Compute overflow areas of current frame relative to aFirstContinuation:
+ nsLayoutUtils::AddBoxesForFrame(aCurrentFrame, &collector);
+ // Return the result in user space:
+ return collector.GetResult() + aFirstContinuationToUserSpace;
+}
+
+bool SVGIntegrationUtils::UsingOverflowAffectingEffects(
+ const nsIFrame* aFrame) {
+ // Currently overflow don't take account of SVG or other non-absolute
+ // positioned clipping, or masking.
+ return aFrame->StyleEffects()->HasFilters();
+}
+
+bool SVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame) {
+ // Even when SVG display lists are disabled, returning true for SVG frames
+ // does not adversely affect any of our callers. Therefore we don't bother
+ // checking the SDL prefs here, since we don't know if we're being called for
+ // painting or hit-testing anyway.
+ const nsStyleSVGReset* style = aFrame->StyleSVGReset();
+ const nsStyleEffects* effects = aFrame->StyleEffects();
+ // TODO(cbrewster): remove backdrop-filter from this list once it is supported
+ // in preserve-3d cases.
+ return effects->HasFilters() || effects->HasBackdropFilters() ||
+ style->HasClipPath() || style->HasMask();
+}
+
+bool SVGIntegrationUtils::UsingMaskOrClipPathForFrame(const nsIFrame* aFrame) {
+ const nsStyleSVGReset* style = aFrame->StyleSVGReset();
+ return style->HasClipPath() || style->HasMask();
+}
+
+bool SVGIntegrationUtils::UsingSimpleClipPathForFrame(const nsIFrame* aFrame) {
+ const nsStyleSVGReset* style = aFrame->StyleSVGReset();
+ if (!style->HasClipPath() || style->HasMask()) {
+ return false;
+ }
+
+ const auto& clipPath = style->mClipPath;
+ if (!clipPath.IsShape()) {
+ return false;
+ }
+
+ return !clipPath.AsShape()._0->IsPolygon();
+}
+
+nsPoint SVGIntegrationUtils::GetOffsetToBoundingBox(nsIFrame* aFrame) {
+ if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ // Do NOT call GetAllInFlowRectsUnion for SVG - it will get the
+ // covered region relative to the SVGOuterSVGFrame, which is absolutely
+ // not what we want. SVG frames are always in user space, so they have
+ // no offset adjustment to make.
+ return nsPoint();
+ }
+
+ // The GetAllInFlowRectsUnion() call gets the union of the frame border-box
+ // rects over all continuations, relative to the origin (top-left of the
+ // border box) of its second argument (here, aFrame, the first continuation).
+ return -nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame).TopLeft();
+}
+
+struct EffectOffsets {
+ // The offset between the reference frame and the bounding box of the
+ // target frame in app unit.
+ nsPoint offsetToBoundingBox;
+ // The offset between the reference frame and the bounding box of the
+ // target frame in app unit.
+ nsPoint offsetToUserSpace;
+ // The offset between the reference frame and the bounding box of the
+ // target frame in device unit.
+ gfxPoint offsetToUserSpaceInDevPx;
+};
+
+static EffectOffsets ComputeEffectOffset(
+ nsIFrame* aFrame, const SVGIntegrationUtils::PaintFramesParams& aParams) {
+ EffectOffsets result;
+
+ result.offsetToBoundingBox =
+ aParams.builder->ToReferenceFrame(aFrame) -
+ SVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
+ if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
+ /* Snap the offset if the reference frame is not a SVG frame,
+ * since other frames will be snapped to pixel when rendering. */
+ result.offsetToBoundingBox =
+ nsPoint(aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(
+ result.offsetToBoundingBox.x),
+ aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(
+ result.offsetToBoundingBox.y));
+ }
+
+ // After applying only "aOffsetToBoundingBox", aParams.ctx would have its
+ // origin at the top left corner of frame's bounding box (over all
+ // continuations).
+ // However, SVG painting needs the origin to be located at the origin of the
+ // SVG frame's "user space", i.e. the space in which, for example, the
+ // frame's BBox lives.
+ // SVG geometry frames and foreignObject frames apply their own offsets, so
+ // their position is relative to their user space. So for these frame types,
+ // if we want aParams.ctx to be in user space, we first need to subtract the
+ // frame's position so that SVG painting can later add it again and the
+ // frame is painted in the right place.
+ gfxPoint toUserSpaceGfx =
+ SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame);
+ nsPoint toUserSpace =
+ nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
+ nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
+
+ result.offsetToUserSpace = result.offsetToBoundingBox - toUserSpace;
+
+#ifdef DEBUG
+ bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
+ NS_ASSERTION(
+ hasSVGLayout || result.offsetToBoundingBox == result.offsetToUserSpace,
+ "For non-SVG frames there shouldn't be any additional offset");
+#endif
+
+ result.offsetToUserSpaceInDevPx = nsLayoutUtils::PointToGfxPoint(
+ result.offsetToUserSpace, aFrame->PresContext()->AppUnitsPerDevPixel());
+
+ return result;
+}
+
+/**
+ * Setup transform matrix of a gfx context by a specific frame. Move the
+ * origin of aParams.ctx to the user space of aFrame.
+ */
+static EffectOffsets MoveContextOriginToUserSpace(
+ nsIFrame* aFrame, const SVGIntegrationUtils::PaintFramesParams& aParams) {
+ EffectOffsets offset = ComputeEffectOffset(aFrame, aParams);
+
+ aParams.ctx.SetMatrixDouble(aParams.ctx.CurrentMatrixDouble().PreTranslate(
+ offset.offsetToUserSpaceInDevPx));
+
+ return offset;
+}
+
+gfxPoint SVGIntegrationUtils::GetOffsetToUserSpaceInDevPx(
+ nsIFrame* aFrame, const PaintFramesParams& aParams) {
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+ EffectOffsets offset = ComputeEffectOffset(firstFrame, aParams);
+ return offset.offsetToUserSpaceInDevPx;
+}
+
+/* static */
+nsSize SVGIntegrationUtils::GetContinuationUnionSize(nsIFrame* aNonSVGFrame) {
+ NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
+ "SVG frames should not get here");
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
+ return nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame).Size();
+}
+
+/* static */ gfx::Size SVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(
+ nsIFrame* aNonSVGFrame) {
+ NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
+ "SVG frames should not get here");
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
+ nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame);
+ return gfx::Size(nsPresContext::AppUnitsToFloatCSSPixels(r.width),
+ nsPresContext::AppUnitsToFloatCSSPixels(r.height));
+}
+
+gfxRect SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(
+ nsIFrame* aNonSVGFrame, bool aUnionContinuations) {
+ // Except for SVGOuterSVGFrame, we shouldn't be getting here with SVG
+ // frames at all. This function is for elements that are laid out using the
+ // CSS box model rules.
+ NS_ASSERTION(!aNonSVGFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
+ "Frames with SVG layout should not get here");
+ MOZ_ASSERT(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG) ||
+ aNonSVGFrame->IsSVGOuterSVGFrame());
+
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
+ // 'r' is in "user space":
+ nsRect r = (aUnionContinuations)
+ ? GetPreEffectsInkOverflowUnion(
+ firstFrame, nullptr, nsRect(),
+ GetOffsetToBoundingBox(firstFrame), false)
+ : GetPreEffectsInkOverflow(firstFrame, aNonSVGFrame,
+ GetOffsetToBoundingBox(firstFrame));
+
+ return nsLayoutUtils::RectToGfxRect(r, AppUnitsPerCSSPixel());
+}
+
+// XXX Since we're called during reflow, this method is broken for frames with
+// continuations. When we're called for a frame with continuations, we're
+// called for each continuation in turn as it's reflowed. However, it isn't
+// until the last continuation is reflowed that this method's
+// GetOffsetToBoundingBox() and GetPreEffectsInkOverflowUnion() calls will
+// obtain valid border boxes for all the continuations. As a result, we'll
+// end up returning bogus post-filter ink overflow rects for all the prior
+// continuations. Unfortunately, by the time the last continuation is
+// reflowed, it's too late to go back and set and propagate the overflow
+// rects on the previous continuations.
+//
+// The reason that we need to pass an override bbox to
+// GetPreEffectsInkOverflowUnion rather than just letting it call into our
+// GetSVGBBoxForNonSVGFrame method is because we get called by
+// ComputeEffectsRect when it has been called with
+// aStoreRectProperties set to false. In this case the pre-effects visual
+// overflow rect that it has been passed may be different to that stored on
+// aFrame, resulting in a different bbox.
+//
+// XXXjwatt The pre-effects ink overflow rect passed to
+// ComputeEffectsRect won't include continuation overflows, so
+// for frames with continuation the following filter analysis will likely end
+// up being carried out with a bbox created as if the frame didn't have
+// continuations.
+//
+// XXXjwatt Using aPreEffectsOverflowRect to create the bbox isn't really right
+// for SVG frames, since for SVG frames the SVG spec defines the bbox to be
+// something quite different to the pre-effects ink overflow rect. However,
+// we're essentially calculating an invalidation area here, and using the
+// pre-effects overflow rect will actually overestimate that area which, while
+// being a bit wasteful, isn't otherwise a problem.
+//
+nsRect SVGIntegrationUtils::ComputePostEffectsInkOverflowRect(
+ nsIFrame* aFrame, const nsRect& aPreEffectsOverflowRect) {
+ MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
+ "Don't call this on SVG child frames");
+
+ MOZ_ASSERT(aFrame->StyleEffects()->HasFilters(),
+ "We should only be called if the frame is filtered, since filters "
+ "are the only effect that affects overflow.");
+
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+ // Note: we do not return here for eHasNoRefs since we must still handle any
+ // CSS filter functions.
+ // TODO: We currently pass nullptr instead of an nsTArray* here, but we
+ // actually should get the filter frames and then pass them into
+ // GetPostFilterBounds below! See bug 1494263.
+ // TODO: we should really return an empty rect for eHasRefsSomeInvalid since
+ // in that case we disable painting of the element.
+ if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) ==
+ SVGObserverUtils::eHasRefsSomeInvalid) {
+ return aPreEffectsOverflowRect;
+ }
+
+ // Create an override bbox - see comment above:
+ nsPoint firstFrameToBoundingBox = GetOffsetToBoundingBox(firstFrame);
+ // overrideBBox is in "user space", in _CSS_ pixels:
+ // XXX Why are we rounding out to pixel boundaries? We don't do that in
+ // GetSVGBBoxForNonSVGFrame, and it doesn't appear to be necessary.
+ gfxRect overrideBBox = nsLayoutUtils::RectToGfxRect(
+ GetPreEffectsInkOverflowUnion(firstFrame, aFrame, aPreEffectsOverflowRect,
+ firstFrameToBoundingBox, true),
+ AppUnitsPerCSSPixel());
+ overrideBBox.RoundOut();
+
+ Maybe<nsRect> overflowRect =
+ FilterInstance::GetPostFilterBounds(firstFrame, &overrideBBox);
+ if (!overflowRect) {
+ return aPreEffectsOverflowRect;
+ }
+
+ // Return overflowRect relative to aFrame, rather than "user space":
+ return overflowRect.value() -
+ (aFrame->GetOffsetTo(firstFrame) + firstFrameToBoundingBox);
+}
+
+nsRect SVGIntegrationUtils::GetRequiredSourceForInvalidArea(
+ nsIFrame* aFrame, const nsRect& aDirtyRect) {
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+
+ // If we have any filters to observe then we should have started doing that
+ // during reflow/ComputeFrameEffectsRect, so we use GetFiltersIfObserving
+ // here to avoid needless work (or masking bugs by setting up observers at
+ // the wrong time).
+ if (!aFrame->StyleEffects()->HasFilters() ||
+ SVGObserverUtils::GetFiltersIfObserving(firstFrame, nullptr) ==
+ SVGObserverUtils::eHasRefsSomeInvalid) {
+ return aDirtyRect;
+ }
+
+ // Convert aDirtyRect into "user space" in app units:
+ nsPoint toUserSpace =
+ aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
+ nsRect postEffectsRect = aDirtyRect + toUserSpace;
+
+ // Return ther result, relative to aFrame, not in user space:
+ return FilterInstance::GetPreFilterNeededArea(firstFrame, postEffectsRect)
+ .GetBounds() -
+ toUserSpace;
+}
+
+bool SVGIntegrationUtils::HitTestFrameForEffects(nsIFrame* aFrame,
+ const nsPoint& aPt) {
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+ // Convert aPt to user space:
+ nsPoint toUserSpace;
+ if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ // XXXmstange Isn't this wrong for svg:use and innerSVG frames?
+ toUserSpace = aFrame->GetPosition();
+ } else {
+ toUserSpace =
+ aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
+ }
+ nsPoint pt = aPt + toUserSpace;
+ gfxPoint userSpacePt = gfxPoint(pt.x, pt.y) / AppUnitsPerCSSPixel();
+ return SVGUtils::HitTestClip(firstFrame, userSpacePt);
+}
+
+using PaintFramesParams = SVGIntegrationUtils::PaintFramesParams;
+
+/**
+ * Paint css-positioned-mask onto a given target(aMaskDT).
+ * Return value indicates if mask is complete.
+ */
+static bool PaintMaskSurface(const PaintFramesParams& aParams,
+ DrawTarget* aMaskDT, float aOpacity,
+ const ComputedStyle* aSC,
+ const nsTArray<SVGMaskFrame*>& aMaskFrames,
+ const nsPoint& aOffsetToUserSpace) {
+ MOZ_ASSERT(aMaskFrames.Length() > 0);
+ MOZ_ASSERT(aMaskDT->GetFormat() == SurfaceFormat::A8);
+ MOZ_ASSERT(aOpacity == 1.0 || aMaskFrames.Length() == 1);
+
+ const nsStyleSVGReset* svgReset = aSC->StyleSVGReset();
+ gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
+
+ nsPresContext* presContext = aParams.frame->PresContext();
+ gfxPoint devPixelOffsetToUserSpace = nsLayoutUtils::PointToGfxPoint(
+ aOffsetToUserSpace, presContext->AppUnitsPerDevPixel());
+
+ RefPtr<gfxContext> maskContext =
+ gfxContext::CreatePreservingTransformOrNull(aMaskDT);
+ MOZ_ASSERT(maskContext);
+
+ bool isMaskComplete = true;
+
+ // Multiple SVG masks interleave with image mask. Paint each layer onto
+ // aMaskDT one at a time.
+ for (int i = aMaskFrames.Length() - 1; i >= 0; i--) {
+ SVGMaskFrame* maskFrame = aMaskFrames[i];
+ CompositionOp compositionOp =
+ (i == int(aMaskFrames.Length() - 1))
+ ? CompositionOp::OP_OVER
+ : nsCSSRendering::GetGFXCompositeMode(
+ svgReset->mMask.mLayers[i].mComposite);
+
+ // maskFrame != nullptr means we get a SVG mask.
+ // maskFrame == nullptr means we get an image mask.
+ if (maskFrame) {
+ SVGMaskFrame::MaskParams params(
+ maskContext->GetDrawTarget(), aParams.frame, cssPxToDevPxMatrix,
+ aOpacity, svgReset->mMask.mLayers[i].mMaskMode, aParams.imgParams);
+ RefPtr<SourceSurface> svgMask = maskFrame->GetMaskForMaskedFrame(params);
+ if (svgMask) {
+ Matrix tmp = aMaskDT->GetTransform();
+ aMaskDT->SetTransform(Matrix());
+ aMaskDT->MaskSurface(ColorPattern(DeviceColor(0.0, 0.0, 0.0, 1.0)),
+ svgMask, Point(0, 0),
+ DrawOptions(1.0, compositionOp));
+ aMaskDT->SetTransform(tmp);
+ }
+ } else if (svgReset->mMask.mLayers[i].mImage.IsResolved()) {
+ gfxContextMatrixAutoSaveRestore matRestore(maskContext);
+
+ maskContext->Multiply(gfxMatrix::Translation(-devPixelOffsetToUserSpace));
+ nsCSSRendering::PaintBGParams params =
+ nsCSSRendering::PaintBGParams::ForSingleLayer(
+ *presContext, aParams.dirtyRect, aParams.borderArea,
+ aParams.frame,
+ aParams.builder->GetBackgroundPaintFlags() |
+ nsCSSRendering::PAINTBG_MASK_IMAGE,
+ i, compositionOp, aOpacity);
+
+ aParams.imgParams.result &= nsCSSRendering::PaintStyleImageLayerWithSC(
+ params, *maskContext, aSC, *aParams.frame->StyleBorder());
+ } else {
+ isMaskComplete = false;
+ }
+ }
+
+ return isMaskComplete;
+}
+
+struct MaskPaintResult {
+ RefPtr<SourceSurface> maskSurface;
+ Matrix maskTransform;
+ bool transparentBlackMask;
+ bool opacityApplied;
+
+ MaskPaintResult() : transparentBlackMask(false), opacityApplied(false) {}
+};
+
+static MaskPaintResult CreateAndPaintMaskSurface(
+ const PaintFramesParams& aParams, float aOpacity, const ComputedStyle* aSC,
+ const nsTArray<SVGMaskFrame*>& aMaskFrames,
+ const nsPoint& aOffsetToUserSpace) {
+ const nsStyleSVGReset* svgReset = aSC->StyleSVGReset();
+ MOZ_ASSERT(aMaskFrames.Length() > 0);
+ MaskPaintResult paintResult;
+
+ gfxContext& ctx = aParams.ctx;
+
+ // Optimization for single SVG mask.
+ if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) {
+ gfxMatrix cssPxToDevPxMatrix =
+ SVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
+ paintResult.opacityApplied = true;
+ SVGMaskFrame::MaskParams params(
+ ctx.GetDrawTarget(), aParams.frame, cssPxToDevPxMatrix, aOpacity,
+ svgReset->mMask.mLayers[0].mMaskMode, aParams.imgParams);
+ paintResult.maskSurface = aMaskFrames[0]->GetMaskForMaskedFrame(params);
+ paintResult.maskTransform = ctx.CurrentMatrix();
+ paintResult.maskTransform.Invert();
+ if (!paintResult.maskSurface) {
+ paintResult.transparentBlackMask = true;
+ }
+
+ return paintResult;
+ }
+
+ const LayoutDeviceRect& maskSurfaceRect =
+ aParams.maskRect.valueOr(LayoutDeviceRect());
+ if (aParams.maskRect.isSome() && maskSurfaceRect.IsEmpty()) {
+ // XXX: Is this ever true?
+ paintResult.transparentBlackMask = true;
+ return paintResult;
+ }
+
+ RefPtr<DrawTarget> maskDT = ctx.GetDrawTarget()->CreateClippedDrawTarget(
+ maskSurfaceRect.ToUnknownRect(), SurfaceFormat::A8);
+ if (!maskDT || !maskDT->IsValid()) {
+ return paintResult;
+ }
+
+ // We can paint mask along with opacity only if
+ // 1. There is only one mask, or
+ // 2. No overlap among masks.
+ // Collision detect in #2 is not that trivial, we only accept #1 here.
+ paintResult.opacityApplied = (aMaskFrames.Length() == 1);
+
+ // Set context's matrix on maskContext, offset by the maskSurfaceRect's
+ // position. This makes sure that we combine the masks in device space.
+ Matrix maskSurfaceMatrix = ctx.CurrentMatrix();
+
+ bool isMaskComplete = PaintMaskSurface(
+ aParams, maskDT, paintResult.opacityApplied ? aOpacity : 1.0, aSC,
+ aMaskFrames, aOffsetToUserSpace);
+
+ if (!isMaskComplete ||
+ (aParams.imgParams.result != ImgDrawResult::SUCCESS &&
+ aParams.imgParams.result != ImgDrawResult::SUCCESS_NOT_COMPLETE &&
+ aParams.imgParams.result != ImgDrawResult::WRONG_SIZE)) {
+ // Now we know the status of mask resource since we used it while painting.
+ // According to the return value of PaintMaskSurface, we know whether mask
+ // resource is resolvable or not.
+ //
+ // For a HTML doc:
+ // According to css-masking spec, always create a mask surface when
+ // we have any item in maskFrame even if all of those items are
+ // non-resolvable <mask-sources> or <images>.
+ // Set paintResult.transparentBlackMask as true, the caller should stop
+ // painting masked content as if this mask is a transparent black one.
+ // For a SVG doc:
+ // SVG 1.1 say that if we fail to resolve a mask, we should draw the
+ // object unmasked.
+ // Left paintResult.maskSurface empty, the caller should paint all
+ // masked content as if this mask is an opaque white one(no mask).
+ paintResult.transparentBlackMask =
+ !aParams.frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
+
+ MOZ_ASSERT(!paintResult.maskSurface);
+ return paintResult;
+ }
+
+ paintResult.maskTransform = maskSurfaceMatrix;
+ if (!paintResult.maskTransform.Invert()) {
+ return paintResult;
+ }
+
+ paintResult.maskSurface = maskDT->Snapshot();
+ return paintResult;
+}
+
+static bool ValidateSVGFrame(nsIFrame* aFrame) {
+#ifdef DEBUG
+ NS_ASSERTION(!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ||
+ (NS_SVGDisplayListPaintingEnabled() &&
+ !aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)),
+ "Should not use SVGIntegrationUtils on this SVG frame");
+#endif
+
+ bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
+ if (hasSVGLayout) {
+#ifdef DEBUG
+ ISVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame);
+ MOZ_ASSERT(svgFrame && aFrame->GetContent()->IsSVGElement(),
+ "A non-SVG frame carries NS_FRAME_SVG_LAYOUT flag?");
+#endif
+
+ const nsIContent* content = aFrame->GetContent();
+ if (!static_cast<const SVGElement*>(content)->HasValidDimensions()) {
+ // The SVG spec says not to draw _anything_
+ return false;
+ }
+ }
+
+ return true;
+}
+
+class AutoPopGroup {
+ public:
+ AutoPopGroup() : mContext(nullptr) {}
+
+ ~AutoPopGroup() {
+ if (mContext) {
+ mContext->PopGroupAndBlend();
+ }
+ }
+
+ void SetContext(gfxContext* aContext) { mContext = aContext; }
+
+ private:
+ gfxContext* mContext;
+};
+
+bool SVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams,
+ bool& aOutIsMaskComplete) {
+ aOutIsMaskComplete = true;
+
+ SVGUtils::MaskUsage maskUsage;
+ SVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity, maskUsage);
+ if (!maskUsage.shouldDoSomething()) {
+ return false;
+ }
+
+ nsIFrame* frame = aParams.frame;
+ if (!ValidateSVGFrame(frame)) {
+ return false;
+ }
+
+ gfxContext& ctx = aParams.ctx;
+ RefPtr<DrawTarget> maskTarget = ctx.GetDrawTarget();
+
+ if (maskUsage.shouldGenerateMaskLayer &&
+ (maskUsage.shouldGenerateClipMaskLayer ||
+ maskUsage.shouldApplyClipPath)) {
+ // We will paint both mask of positioned mask and clip-path into
+ // maskTarget.
+ //
+ // Create one extra draw target for drawing positioned mask, so that we do
+ // not have to copy the content of maskTarget before painting
+ // clip-path into it.
+ maskTarget = maskTarget->CreateClippedDrawTarget(Rect(), SurfaceFormat::A8);
+ }
+
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
+ nsTArray<SVGMaskFrame*> maskFrames;
+ // XXX check return value?
+ SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
+
+ AutoPopGroup autoPop;
+ bool shouldPushOpacity =
+ (maskUsage.opacity != 1.0) && (maskFrames.Length() != 1);
+ if (shouldPushOpacity) {
+ ctx.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, maskUsage.opacity);
+ autoPop.SetContext(&ctx);
+ }
+
+ gfxContextMatrixAutoSaveRestore matSR;
+
+ // Paint clip-path-basic-shape onto ctx
+ gfxContextAutoSaveRestore basicShapeSR;
+ if (maskUsage.shouldApplyBasicShapeOrPath) {
+ matSR.SetContext(&ctx);
+
+ MoveContextOriginToUserSpace(firstFrame, aParams);
+
+ basicShapeSR.SetContext(&ctx);
+ gfxMatrix mat = SVGUtils::GetCSSPxToDevPxMatrix(frame);
+ if (!maskUsage.shouldGenerateMaskLayer) {
+ // Only have basic-shape clip-path effect. Fill clipped region by
+ // opaque white.
+ ctx.SetDeviceColor(DeviceColor::MaskOpaqueWhite());
+ RefPtr<Path> path = CSSClipPathInstance::CreateClipPathForFrame(
+ ctx.GetDrawTarget(), frame, mat);
+ if (path) {
+ ctx.SetPath(path);
+ ctx.Fill();
+ }
+
+ return true;
+ }
+ CSSClipPathInstance::ApplyBasicShapeOrPathClip(ctx, frame, mat);
+ }
+
+ // Paint mask into maskTarget.
+ if (maskUsage.shouldGenerateMaskLayer) {
+ matSR.Restore();
+ matSR.SetContext(&ctx);
+
+ EffectOffsets offsets = ComputeEffectOffset(frame, aParams);
+ maskTarget->SetTransform(maskTarget->GetTransform().PreTranslate(
+ ToPoint(offsets.offsetToUserSpaceInDevPx)));
+ aOutIsMaskComplete = PaintMaskSurface(
+ aParams, maskTarget, shouldPushOpacity ? 1.0 : maskUsage.opacity,
+ firstFrame->Style(), maskFrames, offsets.offsetToUserSpace);
+ }
+
+ // Paint clip-path onto ctx.
+ if (maskUsage.shouldGenerateClipMaskLayer || maskUsage.shouldApplyClipPath) {
+ matSR.Restore();
+ matSR.SetContext(&ctx);
+
+ MoveContextOriginToUserSpace(firstFrame, aParams);
+ Matrix clipMaskTransform;
+ gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(frame);
+
+ SVGClipPathFrame* clipPathFrame;
+ // XXX check return value?
+ SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
+ RefPtr<SourceSurface> maskSurface =
+ maskUsage.shouldGenerateMaskLayer ? maskTarget->Snapshot() : nullptr;
+ clipPathFrame->PaintClipMask(ctx, frame, cssPxToDevPxMatrix, maskSurface);
+ }
+
+ return true;
+}
+
+template <class T>
+void PaintMaskAndClipPathInternal(const PaintFramesParams& aParams,
+ const T& aPaintChild) {
+ MOZ_ASSERT(SVGIntegrationUtils::UsingMaskOrClipPathForFrame(aParams.frame),
+ "Should not use this method when no mask or clipPath effect"
+ "on this frame");
+
+ /* SVG defines the following rendering model:
+ *
+ * 1. Render geometry
+ * 2. Apply filter
+ * 3. Apply clipping, masking, group opacity
+ *
+ * We handle #3 here and perform a couple of optimizations:
+ *
+ * + Use cairo's clipPath when representable natively (single object
+ * clip region).
+ *
+ * + Merge opacity and masking if both used together.
+ */
+ nsIFrame* frame = aParams.frame;
+ if (!ValidateSVGFrame(frame)) {
+ return;
+ }
+
+ SVGUtils::MaskUsage maskUsage;
+ SVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity, maskUsage);
+
+ if (maskUsage.opacity == 0.0f) {
+ return;
+ }
+
+ gfxContext& context = aParams.ctx;
+ gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context);
+
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
+
+ SVGClipPathFrame* clipPathFrame;
+ // XXX check return value?
+ SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
+
+ nsTArray<SVGMaskFrame*> maskFrames;
+ // XXX check return value?
+ SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
+
+ gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(frame);
+
+ bool shouldGenerateMask =
+ (maskUsage.opacity != 1.0f || maskUsage.shouldGenerateClipMaskLayer ||
+ maskUsage.shouldGenerateMaskLayer);
+ bool shouldPushMask = false;
+
+ /* Check if we need to do additional operations on this child's
+ * rendering, which necessitates rendering into another surface. */
+ if (shouldGenerateMask) {
+ gfxContextMatrixAutoSaveRestore matSR;
+
+ RefPtr<SourceSurface> maskSurface;
+ bool opacityApplied = false;
+
+ if (maskUsage.shouldGenerateMaskLayer) {
+ matSR.SetContext(&context);
+
+ // For css-mask, we want to generate a mask for each continuation frame,
+ // so we setup context matrix by the position of the current frame,
+ // instead of the first continuation frame.
+ EffectOffsets offsets = MoveContextOriginToUserSpace(frame, aParams);
+ MaskPaintResult paintResult = CreateAndPaintMaskSurface(
+ aParams, maskUsage.opacity, firstFrame->Style(), maskFrames,
+ offsets.offsetToUserSpace);
+
+ if (paintResult.transparentBlackMask) {
+ return;
+ }
+
+ maskSurface = paintResult.maskSurface;
+ if (maskSurface) {
+ shouldPushMask = true;
+
+ opacityApplied = paintResult.opacityApplied;
+ }
+ }
+
+ if (maskUsage.shouldGenerateClipMaskLayer) {
+ matSR.Restore();
+ matSR.SetContext(&context);
+
+ MoveContextOriginToUserSpace(firstFrame, aParams);
+ RefPtr<SourceSurface> clipMaskSurface = clipPathFrame->GetClipMask(
+ context, frame, cssPxToDevPxMatrix, maskSurface);
+
+ if (clipMaskSurface) {
+ maskSurface = clipMaskSurface;
+ } else {
+ // Either entire surface is clipped out, or gfx buffer allocation
+ // failure in SVGClipPathFrame::GetClipMask.
+ return;
+ }
+
+ shouldPushMask = true;
+ }
+
+ // opacity != 1.0f.
+ if (!maskUsage.shouldGenerateClipMaskLayer &&
+ !maskUsage.shouldGenerateMaskLayer) {
+ MOZ_ASSERT(maskUsage.opacity != 1.0f);
+
+ matSR.SetContext(&context);
+ MoveContextOriginToUserSpace(firstFrame, aParams);
+ shouldPushMask = true;
+ }
+
+ if (shouldPushMask) {
+ // We want the mask to be untransformed so use the inverse of the
+ // current transform as the maskTransform to compensate.
+ Matrix maskTransform = context.CurrentMatrix();
+ maskTransform.Invert();
+
+ context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
+ opacityApplied ? 1.0 : maskUsage.opacity,
+ maskSurface, maskTransform);
+ }
+ }
+
+ /* If this frame has only a trivial clipPath, set up cairo's clipping now so
+ * we can just do normal painting and get it clipped appropriately.
+ */
+ if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
+ gfxContextMatrixAutoSaveRestore matSR(&context);
+
+ MoveContextOriginToUserSpace(firstFrame, aParams);
+
+ MOZ_ASSERT(!maskUsage.shouldApplyClipPath ||
+ !maskUsage.shouldApplyBasicShapeOrPath);
+ if (maskUsage.shouldApplyClipPath) {
+ clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix);
+ } else {
+ CSSClipPathInstance::ApplyBasicShapeOrPathClip(context, frame,
+ cssPxToDevPxMatrix);
+ }
+ }
+
+ /* Paint the child */
+ context.SetMatrix(matrixAutoSaveRestore.Matrix());
+ aPaintChild();
+
+ if (StaticPrefs::layers_draw_mask_debug()) {
+ gfxContextAutoSaveRestore saver(&context);
+
+ context.NewPath();
+ gfxRect drawingRect = nsLayoutUtils::RectToGfxRect(
+ aParams.borderArea, frame->PresContext()->AppUnitsPerDevPixel());
+ context.SnappedRectangle(drawingRect);
+ sRGBColor overlayColor(0.0f, 0.0f, 0.0f, 0.8f);
+ if (maskUsage.shouldGenerateMaskLayer) {
+ overlayColor.r = 1.0f; // red represents css positioned mask.
+ }
+ if (maskUsage.shouldApplyClipPath ||
+ maskUsage.shouldGenerateClipMaskLayer) {
+ overlayColor.g = 1.0f; // green represents clip-path:<clip-source>.
+ }
+ if (maskUsage.shouldApplyBasicShapeOrPath) {
+ overlayColor.b = 1.0f; // blue represents
+ // clip-path:<basic-shape>||<geometry-box>.
+ }
+
+ context.SetColor(overlayColor);
+ context.Fill();
+ }
+
+ if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
+ context.PopClip();
+ }
+
+ if (shouldPushMask) {
+ context.PopGroupAndBlend();
+ }
+}
+
+void SVGIntegrationUtils::PaintMaskAndClipPath(
+ const PaintFramesParams& aParams,
+ const std::function<void()>& aPaintChild) {
+ PaintMaskAndClipPathInternal(aParams, aPaintChild);
+}
+
+void SVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams,
+ Span<const StyleFilter> aFilters,
+ const SVGFilterPaintCallback& aCallback) {
+ MOZ_ASSERT(!aParams.builder->IsForGenerateGlyphMask(),
+ "Filter effect is discarded while generating glyph mask.");
+ MOZ_ASSERT(!aFilters.IsEmpty(),
+ "Should not use this method when no filter effect on this frame");
+
+ nsIFrame* frame = aParams.frame;
+ if (!ValidateSVGFrame(frame)) {
+ return;
+ }
+
+ float opacity = SVGUtils::ComputeOpacity(frame, aParams.handleOpacity);
+ if (opacity == 0.0f) {
+ return;
+ }
+
+ // Properties are added lazily and may have been removed by a restyle, so make
+ // sure all applicable ones are set again.
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
+ // Note: we do not return here for eHasNoRefs since we must still handle any
+ // CSS filter functions.
+ // TODO: We currently pass nullptr instead of an nsTArray* here, but we
+ // actually should get the filter frames and then pass them into
+ // PaintFilteredFrame below! See bug 1494263.
+ // XXX: Do we need to check for eHasRefsSomeInvalid here given that
+ // nsDisplayFilter::BuildLayer returns nullptr for eHasRefsSomeInvalid?
+ // Or can we just assert !eHasRefsSomeInvalid?
+ if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) ==
+ SVGObserverUtils::eHasRefsSomeInvalid) {
+ aCallback(aParams.ctx, aParams.imgParams, nullptr, nullptr);
+ return;
+ }
+
+ gfxContext& context = aParams.ctx;
+
+ gfxContextAutoSaveRestore autoSR(&context);
+ EffectOffsets offsets = MoveContextOriginToUserSpace(firstFrame, aParams);
+
+ /* Paint the child and apply filters */
+ nsRegion dirtyRegion = aParams.dirtyRect - offsets.offsetToBoundingBox;
+
+ FilterInstance::PaintFilteredFrame(frame, aFilters, &context, aCallback,
+ &dirtyRegion, aParams.imgParams, opacity);
+}
+
+bool SVGIntegrationUtils::CreateWebRenderCSSFilters(
+ Span<const StyleFilter> aFilters, nsIFrame* aFrame,
+ WrFiltersHolder& aWrFilters) {
+ // All CSS filters are supported by WebRender. SVG filters are not fully
+ // supported, those use NS_STYLE_FILTER_URL and are handled separately.
+
+ // If there are too many filters to render, then just pretend that we
+ // succeeded, and don't render any of them.
+ if (aFilters.Length() >
+ StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
+ return true;
+ }
+ aWrFilters.filters.SetCapacity(aFilters.Length());
+ auto& wrFilters = aWrFilters.filters;
+ for (const StyleFilter& filter : aFilters) {
+ switch (filter.tag) {
+ case StyleFilter::Tag::Brightness:
+ wrFilters.AppendElement(
+ wr::FilterOp::Brightness(filter.AsBrightness()));
+ break;
+ case StyleFilter::Tag::Contrast:
+ wrFilters.AppendElement(wr::FilterOp::Contrast(filter.AsContrast()));
+ break;
+ case StyleFilter::Tag::Grayscale:
+ wrFilters.AppendElement(wr::FilterOp::Grayscale(filter.AsGrayscale()));
+ break;
+ case StyleFilter::Tag::Invert:
+ wrFilters.AppendElement(wr::FilterOp::Invert(filter.AsInvert()));
+ break;
+ case StyleFilter::Tag::Opacity: {
+ float opacity = filter.AsOpacity();
+ wrFilters.AppendElement(wr::FilterOp::Opacity(
+ wr::PropertyBinding<float>::Value(opacity), opacity));
+ break;
+ }
+ case StyleFilter::Tag::Saturate:
+ wrFilters.AppendElement(wr::FilterOp::Saturate(filter.AsSaturate()));
+ break;
+ case StyleFilter::Tag::Sepia:
+ wrFilters.AppendElement(wr::FilterOp::Sepia(filter.AsSepia()));
+ break;
+ case StyleFilter::Tag::HueRotate: {
+ wrFilters.AppendElement(
+ wr::FilterOp::HueRotate(filter.AsHueRotate().ToDegrees()));
+ break;
+ }
+ case StyleFilter::Tag::Blur: {
+ // TODO(emilio): we should go directly from css pixels -> device pixels.
+ float appUnitsPerDevPixel =
+ aFrame->PresContext()->AppUnitsPerDevPixel();
+ float radius = NSAppUnitsToFloatPixels(filter.AsBlur().ToAppUnits(),
+ appUnitsPerDevPixel);
+ wrFilters.AppendElement(wr::FilterOp::Blur(radius, radius));
+ break;
+ }
+ case StyleFilter::Tag::DropShadow: {
+ float appUnitsPerDevPixel =
+ aFrame->PresContext()->AppUnitsPerDevPixel();
+ const StyleSimpleShadow& shadow = filter.AsDropShadow();
+ nscolor color = shadow.color.CalcColor(aFrame);
+
+ wr::Shadow wrShadow;
+ wrShadow.offset = {
+ NSAppUnitsToFloatPixels(shadow.horizontal.ToAppUnits(),
+ appUnitsPerDevPixel),
+ NSAppUnitsToFloatPixels(shadow.vertical.ToAppUnits(),
+ appUnitsPerDevPixel)};
+ wrShadow.blur_radius = NSAppUnitsToFloatPixels(shadow.blur.ToAppUnits(),
+ appUnitsPerDevPixel);
+ wrShadow.color = {NS_GET_R(color) / 255.0f, NS_GET_G(color) / 255.0f,
+ NS_GET_B(color) / 255.0f, NS_GET_A(color) / 255.0f};
+ wrFilters.AppendElement(wr::FilterOp::DropShadow(wrShadow));
+ break;
+ }
+ default:
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool SVGIntegrationUtils::BuildWebRenderFilters(
+ nsIFrame* aFilteredFrame, Span<const StyleFilter> aFilters,
+ WrFiltersHolder& aWrFilters, bool& aInitialized) {
+ return FilterInstance::BuildWebRenderFilters(aFilteredFrame, aFilters,
+ aWrFilters, aInitialized);
+}
+
+bool SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(nsIFrame* aFrame) {
+ WrFiltersHolder wrFilters;
+ auto filterChain = aFrame->StyleEffects()->mFilters.AsSpan();
+ bool initialized = true;
+ return CreateWebRenderCSSFilters(filterChain, aFrame, wrFilters) ||
+ BuildWebRenderFilters(aFrame, filterChain, wrFilters, initialized);
+}
+
+bool SVGIntegrationUtils::UsesSVGEffectsNotSupportedInCompositor(
+ nsIFrame* aFrame) {
+ // WebRender supports masks / clip-paths and some filters in the compositor.
+ if (aFrame->StyleEffects()->HasFilters()) {
+ return !SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(aFrame);
+ }
+ return false;
+}
+
+class PaintFrameCallback : public gfxDrawingCallback {
+ public:
+ PaintFrameCallback(nsIFrame* aFrame, const nsSize aPaintServerSize,
+ const IntSize aRenderSize, uint32_t aFlags)
+ : mFrame(aFrame),
+ mPaintServerSize(aPaintServerSize),
+ mRenderSize(aRenderSize),
+ mFlags(aFlags) {}
+ virtual bool operator()(gfxContext* aContext, const gfxRect& aFillRect,
+ const SamplingFilter aSamplingFilter,
+ const gfxMatrix& aTransform) override;
+
+ private:
+ nsIFrame* mFrame;
+ nsSize mPaintServerSize;
+ IntSize mRenderSize;
+ uint32_t mFlags;
+};
+
+bool PaintFrameCallback::operator()(gfxContext* aContext,
+ const gfxRect& aFillRect,
+ const SamplingFilter aSamplingFilter,
+ const gfxMatrix& aTransform) {
+ if (mFrame->HasAnyStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER)) {
+ return false;
+ }
+
+ AutoSetRestorePaintServerState paintServer(mFrame);
+
+ aContext->Save();
+
+ // Clip to aFillRect so that we don't paint outside.
+ aContext->Clip(aFillRect);
+
+ gfxMatrix invmatrix = aTransform;
+ if (!invmatrix.Invert()) {
+ return false;
+ }
+ aContext->Multiply(invmatrix);
+
+ // nsLayoutUtils::PaintFrame will anchor its painting at mFrame. But we want
+ // to have it anchored at the top left corner of the bounding box of all of
+ // mFrame's continuations. So we add a translation transform.
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ nsPoint offset = SVGIntegrationUtils::GetOffsetToBoundingBox(mFrame);
+ gfxPoint devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
+ aContext->Multiply(gfxMatrix::Translation(devPxOffset));
+
+ gfxSize paintServerSize =
+ gfxSize(mPaintServerSize.width, mPaintServerSize.height) /
+ mFrame->PresContext()->AppUnitsPerDevPixel();
+
+ // nsLayoutUtils::PaintFrame wants to render with paintServerSize, but we
+ // want it to render with mRenderSize, so we need to set up a scale transform.
+ gfxFloat scaleX = mRenderSize.width / paintServerSize.width;
+ gfxFloat scaleY = mRenderSize.height / paintServerSize.height;
+ aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY));
+
+ // Draw.
+ nsRect dirty(-offset.x, -offset.y, mPaintServerSize.width,
+ mPaintServerSize.height);
+
+ using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
+ PaintFrameFlags flags = PaintFrameFlags::InTransform;
+ if (mFlags & SVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES) {
+ flags |= PaintFrameFlags::SyncDecodeImages;
+ }
+ nsLayoutUtils::PaintFrame(aContext, mFrame, dirty, NS_RGBA(0, 0, 0, 0),
+ nsDisplayListBuilderMode::Painting, flags);
+
+ nsIFrame* currentFrame = mFrame;
+ while ((currentFrame = currentFrame->GetNextContinuation()) != nullptr) {
+ offset = currentFrame->GetOffsetToCrossDoc(mFrame);
+ devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
+
+ aContext->Save();
+ aContext->Multiply(gfxMatrix::Scaling(1 / scaleX, 1 / scaleY));
+ aContext->Multiply(gfxMatrix::Translation(devPxOffset));
+ aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY));
+
+ nsLayoutUtils::PaintFrame(aContext, currentFrame, dirty - offset,
+ NS_RGBA(0, 0, 0, 0),
+ nsDisplayListBuilderMode::Painting, flags);
+
+ aContext->Restore();
+ }
+
+ aContext->Restore();
+
+ return true;
+}
+
+/* static */
+already_AddRefed<gfxDrawable> SVGIntegrationUtils::DrawableFromPaintServer(
+ nsIFrame* aFrame, nsIFrame* aTarget, const nsSize& aPaintServerSize,
+ const IntSize& aRenderSize, const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix, uint32_t aFlags) {
+ // aPaintServerSize is the size that would be filled when using
+ // background-repeat:no-repeat and background-size:auto. For normal background
+ // images, this would be the intrinsic size of the image; for gradients and
+ // patterns this would be the whole target frame fill area.
+ // aRenderSize is what we will be actually filling after accounting for
+ // background-size.
+ if (SVGPaintServerFrame* server = do_QueryFrame(aFrame)) {
+ // aFrame is either a pattern or a gradient. These fill the whole target
+ // frame by default, so aPaintServerSize is the whole target background fill
+ // area.
+ gfxRect overrideBounds(0, 0, aPaintServerSize.width,
+ aPaintServerSize.height);
+ overrideBounds.Scale(1.0 / aFrame->PresContext()->AppUnitsPerDevPixel());
+ imgDrawingParams imgParams(aFlags);
+ RefPtr<gfxPattern> pattern = server->GetPaintServerPattern(
+ aTarget, aDrawTarget, aContextMatrix, &nsStyleSVG::mFill, 1.0,
+ imgParams, &overrideBounds);
+
+ if (!pattern) {
+ return nullptr;
+ }
+
+ // pattern is now set up to fill aPaintServerSize. But we want it to
+ // fill aRenderSize, so we need to add a scaling transform.
+ // We couldn't just have set overrideBounds to aRenderSize - it would have
+ // worked for gradients, but for patterns it would result in a different
+ // pattern size.
+ gfxFloat scaleX = overrideBounds.Width() / aRenderSize.width;
+ gfxFloat scaleY = overrideBounds.Height() / aRenderSize.height;
+ gfxMatrix scaleMatrix = gfxMatrix::Scaling(scaleX, scaleY);
+ pattern->SetMatrix(scaleMatrix * pattern->GetMatrix());
+ RefPtr<gfxDrawable> drawable = new gfxPatternDrawable(pattern, aRenderSize);
+ return drawable.forget();
+ }
+
+ if (aFrame->IsFrameOfType(nsIFrame::eSVG) &&
+ !static_cast<ISVGDisplayableFrame*>(do_QueryFrame(aFrame))) {
+ MOZ_ASSERT_UNREACHABLE(
+ "We should prevent painting of unpaintable SVG "
+ "before we get here");
+ return nullptr;
+ }
+
+ // We don't want to paint into a surface as long as we don't need to, so we
+ // set up a drawing callback.
+ RefPtr<gfxDrawingCallback> cb =
+ new PaintFrameCallback(aFrame, aPaintServerSize, aRenderSize, aFlags);
+ RefPtr<gfxDrawable> drawable = new gfxCallbackDrawable(cb, aRenderSize);
+ return drawable.forget();
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGIntegrationUtils.h b/layout/svg/SVGIntegrationUtils.h
new file mode 100644
index 0000000000..c736727d2b
--- /dev/null
+++ b/layout/svg/SVGIntegrationUtils.h
@@ -0,0 +1,269 @@
+/* -*- 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 LAYOUT_SVG_SVGINTEGRATIONUTILS_H_
+#define LAYOUT_SVG_SVGINTEGRATIONUTILS_H_
+
+#include "ImgDrawResult.h"
+#include "gfxMatrix.h"
+#include "gfxRect.h"
+#include "nsRegionFwd.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+
+class gfxContext;
+class gfxDrawable;
+class nsIFrame;
+struct nsPoint;
+struct nsRect;
+struct nsSize;
+
+struct WrFiltersHolder {
+ nsTArray<mozilla::wr::FilterOp> filters;
+ nsTArray<mozilla::wr::WrFilterData> filter_datas;
+ mozilla::Maybe<nsRect> post_filters_clip;
+ // This exists just to own the values long enough for them to be copied into
+ // rust.
+ nsTArray<nsTArray<float>> values;
+};
+
+namespace mozilla {
+class nsDisplayList;
+class nsDisplayListBuilder;
+
+namespace gfx {
+class DrawTarget;
+} // namespace gfx
+
+/**
+ * Integration of SVG effects (clipPath clipping, masking and filters) into
+ * regular display list based painting and hit-testing.
+ */
+class SVGIntegrationUtils final {
+ using DrawTarget = gfx::DrawTarget;
+ using IntRect = gfx::IntRect;
+ using imgDrawingParams = image::imgDrawingParams;
+
+ public:
+ /**
+ * Returns true if SVG effects that affect the overflow of the given frame
+ * are currently applied to the frame.
+ */
+ static bool UsingOverflowAffectingEffects(const nsIFrame* aFrame);
+
+ /**
+ * Returns true if SVG effects are currently applied to this frame.
+ */
+ static bool UsingEffectsForFrame(const nsIFrame* aFrame);
+
+ /**
+ * Returns true if mask or clippath are currently applied to this frame.
+ */
+ static bool UsingMaskOrClipPathForFrame(const nsIFrame* aFrame);
+
+ /**
+ * Returns true if the element has a clippath that is simple enough to
+ * be represented without a mask in WebRender.
+ */
+ static bool UsingSimpleClipPathForFrame(const nsIFrame* aFrame);
+
+ /**
+ * Returns the size of the union of the border-box rects of all of
+ * aNonSVGFrame's continuations.
+ */
+ static nsSize GetContinuationUnionSize(nsIFrame* aNonSVGFrame);
+
+ /**
+ * When SVG effects need to resolve percentage, userSpaceOnUse lengths, they
+ * need a coordinate context to resolve them against. This method provides
+ * that coordinate context for non-SVG frames with SVG effects applied to
+ * them. The gfxSize returned is the size of the union of all of the given
+ * frame's continuations' border boxes, converted to SVG user units (equal to
+ * CSS px units), as required by the SVG code.
+ */
+ static gfx::Size GetSVGCoordContextForNonSVGFrame(nsIFrame* aNonSVGFrame);
+
+ /**
+ * SVG effects such as SVG filters, masking and clipPath may require an SVG
+ * "bbox" for the element they're being applied to in order to make decisions
+ * about positioning, and to resolve various lengths against. This method
+ * provides the "bbox" for non-SVG frames. The bbox returned is in CSS px
+ * units, and aUnionContinuations decide whether bbox contains the area of
+ * current frame only or the union of all aNonSVGFrame's continuations'
+ * overflow areas, relative to the top-left of the union of all aNonSVGFrame's
+ * continuations' border box rects.
+ */
+ static gfxRect GetSVGBBoxForNonSVGFrame(nsIFrame* aNonSVGFrame,
+ bool aUnionContinuations);
+
+ /**
+ * Used to adjust a frame's pre-effects ink overflow rect to take account
+ * of SVG effects.
+ *
+ * XXX This method will not do the right thing for frames with continuations.
+ * It really needs all the continuations to have been reflowed before being
+ * called, but we currently call it on each continuation as its overflow
+ * rects are set during the reflow of each particular continuation. Gecko's
+ * current reflow architecture does not allow us to set the overflow rects
+ * for a whole chain of continuations for a given element at the point when
+ * the last continuation is reflowed. See:
+ * http://groups.google.com/group/mozilla.dev.tech.layout/msg/6b179066f3051f65
+ */
+ static nsRect ComputePostEffectsInkOverflowRect(
+ nsIFrame* aFrame, const nsRect& aPreEffectsOverflowRect);
+
+ /**
+ * Figure out which area of the source is needed given an area to
+ * repaint
+ */
+ static nsRect GetRequiredSourceForInvalidArea(nsIFrame* aFrame,
+ const nsRect& aDirtyRect);
+
+ /**
+ * Returns true if the given point is not clipped out by effects.
+ * @param aPt in appunits relative to aFrame
+ */
+ static bool HitTestFrameForEffects(nsIFrame* aFrame, const nsPoint& aPt);
+
+ struct MOZ_STACK_CLASS PaintFramesParams {
+ gfxContext& ctx;
+ nsIFrame* frame;
+ nsRect dirtyRect;
+ nsRect borderArea;
+ nsDisplayListBuilder* builder;
+ bool handleOpacity; // If true, PaintMaskAndClipPath/ PaintFilter should
+ // apply css opacity.
+ Maybe<LayoutDeviceRect> maskRect;
+ imgDrawingParams& imgParams;
+
+ explicit PaintFramesParams(gfxContext& aCtx, nsIFrame* aFrame,
+ const nsRect& aDirtyRect,
+ const nsRect& aBorderArea,
+ nsDisplayListBuilder* aBuilder,
+ bool aHandleOpacity,
+ imgDrawingParams& aImgParams)
+ : ctx(aCtx),
+ frame(aFrame),
+ dirtyRect(aDirtyRect),
+ borderArea(aBorderArea),
+ builder(aBuilder),
+ handleOpacity(aHandleOpacity),
+ imgParams(aImgParams) {}
+ };
+
+ // This should use FunctionRef instead of std::function because we don't need
+ // to take ownership of the function. See bug 1490781.
+ static void PaintMaskAndClipPath(const PaintFramesParams& aParams,
+ const std::function<void()>& aPaintChild);
+
+ /**
+ * Paint mask of frame onto a given context, aParams.ctx.
+ * aParams.ctx must contain an A8 surface. Returns false if the mask
+ * didn't get painted and should be ignored at the call site.
+ * isMaskComplete is an outparameter returning whether the mask is complete.
+ * Incomplete masks should not be drawn and the proper fallback behaviour
+ * depends on if the masked element is html or svg.
+ */
+ static bool PaintMask(const PaintFramesParams& aParams,
+ bool& aOutIsMaskComplete);
+
+ /**
+ * Paint the frame contents.
+ * SVG frames will have had matrix propagation set to false already.
+ * Non-SVG frames have to do their own thing.
+ * The caller will do a Save()/Restore() as necessary so feel free
+ * to mess with context state.
+ * The context will be configured to use the "user space" coordinate
+ * system if passing aTransform/aDirtyRect, or untouched otherwise.
+ * @param aImgParams the params to draw with.
+ * @param aTransform the user-to-device space matrix, if painting with
+ * filters.
+ * @param aDirtyRect the dirty rect *in user space pixels*
+ */
+ using SVGFilterPaintCallback = std::function<void(
+ gfxContext& aContext, imgDrawingParams&, const gfxMatrix* aTransform,
+ const nsIntRect* aDirtyRect)>;
+
+ /**
+ * Paint non-SVG frame with filter and opacity effect.
+ */
+ static void PaintFilter(const PaintFramesParams& aParams,
+ Span<const StyleFilter> aFilters,
+ const SVGFilterPaintCallback& aCallback);
+
+ /**
+ * Build WebRender filters for a frame with CSS filters applied to it.
+ */
+ static bool CreateWebRenderCSSFilters(Span<const StyleFilter> aFilters,
+ nsIFrame* aFrame,
+ WrFiltersHolder& aWrFilters);
+
+ /**
+ * Try to build WebRender filters for a frame with SVG filters applied to it
+ * if the filters are supported.
+ */
+ static bool BuildWebRenderFilters(nsIFrame* aFilteredFrame,
+ Span<const StyleFilter> aFilters,
+ WrFiltersHolder& aWrFilters,
+ bool& aInitialized);
+
+ /**
+ * Check if the filters present on |aFrame| are supported by WebRender.
+ */
+ static bool CanCreateWebRenderFiltersForFrame(nsIFrame* aFrame);
+
+ /**
+ * Check if |aFrame| uses any SVG effects that cannot be rendered in the
+ * compositor.
+ */
+ static bool UsesSVGEffectsNotSupportedInCompositor(nsIFrame* aFrame);
+
+ /**
+ * @param aRenderingContext the target rendering context in which the paint
+ * server will be rendered
+ * @param aTarget the target frame onto which the paint server will be
+ * rendered
+ * @param aPaintServer a first-continuation frame to use as the source
+ * @param aFilter a filter to be applied when scaling
+ * @param aDest the area the paint server image should be mapped to
+ * @param aFill the area to be filled with copies of the paint server image
+ * @param aAnchor a point in aFill which we will ensure is pixel-aligned in
+ * the output
+ * @param aDirty pixels outside this area may be skipped
+ * @param aPaintServerSize the size that would be filled when using
+ * background-repeat:no-repeat and background-size:auto. For normal background
+ * images, this would be the intrinsic size of the image; for gradients and
+ * patterns this would be the whole target frame fill area.
+ * @param aFlags pass FLAG_SYNC_DECODE_IMAGES and any images in the paint
+ * server will be decoding synchronously if they are not decoded already.
+ */
+ enum {
+ FLAG_SYNC_DECODE_IMAGES = 0x01,
+ };
+
+ static already_AddRefed<gfxDrawable> DrawableFromPaintServer(
+ nsIFrame* aFrame, nsIFrame* aTarget, const nsSize& aPaintServerSize,
+ const gfx::IntSize& aRenderSize, const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix, uint32_t aFlags);
+
+ /**
+ * For non-SVG frames, this gives the offset to the frame's "user space".
+ * For SVG frames, this returns a zero offset.
+ */
+ static nsPoint GetOffsetToBoundingBox(nsIFrame* aFrame);
+
+ /**
+ * The offset between the reference frame and the bounding box of the
+ * target frame in device units.
+ */
+ static gfxPoint GetOffsetToUserSpaceInDevPx(nsIFrame* aFrame,
+ const PaintFramesParams& aParams);
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGINTEGRATIONUTILS_H_
diff --git a/layout/svg/SVGMarkerFrame.cpp b/layout/svg/SVGMarkerFrame.cpp
new file mode 100644
index 0000000000..b8df132fb7
--- /dev/null
+++ b/layout/svg/SVGMarkerFrame.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/. */
+
+// Main header first:
+#include "SVGMarkerFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfxContext.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGGeometryFrame.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/SVGGeometryElement.h"
+#include "mozilla/dom/SVGMarkerElement.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+nsContainerFrame* NS_NewSVGMarkerFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGMarkerFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGMarkerFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods:
+
+nsresult SVGMarkerFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::markerUnits || aAttribute == nsGkAtoms::refX ||
+ aAttribute == nsGkAtoms::refY || aAttribute == nsGkAtoms::markerWidth ||
+ aAttribute == nsGkAtoms::markerHeight ||
+ aAttribute == nsGkAtoms::orient ||
+ aAttribute == nsGkAtoms::preserveAspectRatio ||
+ aAttribute == nsGkAtoms::viewBox)) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(this);
+ }
+
+ return SVGContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+}
+
+#ifdef DEBUG
+void SVGMarkerFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::marker),
+ "Content is not an SVG marker");
+
+ SVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+//----------------------------------------------------------------------
+// SVGContainerFrame methods:
+
+gfxMatrix SVGMarkerFrame::GetCanvasTM() {
+ NS_ASSERTION(mMarkedFrame, "null SVGGeometry frame");
+
+ if (mInUse2) {
+ // We're going to be bailing drawing the marker, so return an identity.
+ return gfxMatrix();
+ }
+
+ SVGMarkerElement* content = static_cast<SVGMarkerElement*>(GetContent());
+
+ mInUse2 = true;
+ gfxMatrix markedTM = mMarkedFrame->GetCanvasTM();
+ mInUse2 = false;
+
+ Matrix viewBoxTM = content->GetViewBoxTransform();
+
+ return ThebesMatrix(viewBoxTM * mMarkerTM) * markedTM;
+}
+
+static nsIFrame* GetAnonymousChildFrame(nsIFrame* aFrame) {
+ nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
+ MOZ_ASSERT(kid && kid->IsSVGMarkerAnonChildFrame(),
+ "expected to find anonymous child of marker frame");
+ return kid;
+}
+
+void SVGMarkerFrame::PaintMark(gfxContext& aContext,
+ const gfxMatrix& aToMarkedFrameUserSpace,
+ SVGGeometryFrame* aMarkedFrame,
+ const SVGMark& aMark, float aStrokeWidth,
+ imgDrawingParams& aImgParams) {
+ // If the flag is set when we get here, it means this marker frame
+ // has already been used painting the current mark, and the document
+ // has a marker reference loop.
+ if (mInUse) {
+ return;
+ }
+
+ AutoMarkerReferencer markerRef(this, aMarkedFrame);
+
+ SVGMarkerElement* marker = static_cast<SVGMarkerElement*>(GetContent());
+ if (!marker->HasValidDimensions()) {
+ return;
+ }
+
+ const SVGViewBox viewBox = marker->GetViewBox();
+
+ if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) {
+ // We must disable rendering if the viewBox width or height are zero.
+ return;
+ }
+
+ Matrix viewBoxTM = marker->GetViewBoxTransform();
+
+ mMarkerTM = marker->GetMarkerTransform(aStrokeWidth, aMark);
+
+ gfxMatrix markTM = ThebesMatrix(viewBoxTM) * ThebesMatrix(mMarkerTM) *
+ aToMarkedFrameUserSpace;
+
+ if (StyleDisplay()->IsScrollableOverflow()) {
+ aContext.Save();
+ gfxRect clipRect = SVGUtils::GetClipRectForFrame(
+ this, viewBox.x, viewBox.y, viewBox.width, viewBox.height);
+ SVGUtils::SetClipRect(&aContext, markTM, clipRect);
+ }
+
+ nsIFrame* kid = GetAnonymousChildFrame(this);
+ ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
+ // The CTM of each frame referencing us may be different.
+ SVGFrame->NotifySVGChanged(ISVGDisplayableFrame::TRANSFORM_CHANGED);
+ SVGUtils::PaintFrameWithEffects(kid, aContext, markTM, aImgParams);
+
+ if (StyleDisplay()->IsScrollableOverflow()) aContext.Restore();
+}
+
+SVGBBox SVGMarkerFrame::GetMarkBBoxContribution(const Matrix& aToBBoxUserspace,
+ uint32_t aFlags,
+ SVGGeometryFrame* aMarkedFrame,
+ const SVGMark& aMark,
+ float aStrokeWidth) {
+ SVGBBox bbox;
+
+ // If the flag is set when we get here, it means this marker frame
+ // has already been used in calculating the current mark bbox, and
+ // the document has a marker reference loop.
+ if (mInUse) {
+ return bbox;
+ }
+
+ AutoMarkerReferencer markerRef(this, aMarkedFrame);
+
+ SVGMarkerElement* content = static_cast<SVGMarkerElement*>(GetContent());
+ if (!content->HasValidDimensions()) {
+ return bbox;
+ }
+
+ const SVGViewBox viewBox = content->GetViewBox();
+
+ if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) {
+ return bbox;
+ }
+
+ mMarkerTM = content->GetMarkerTransform(aStrokeWidth, aMark);
+ Matrix viewBoxTM = content->GetViewBoxTransform();
+
+ Matrix tm = viewBoxTM * mMarkerTM * aToBBoxUserspace;
+
+ ISVGDisplayableFrame* child = do_QueryFrame(GetAnonymousChildFrame(this));
+ // When we're being called to obtain the invalidation area, we need to
+ // pass down all the flags so that stroke is included. However, once DOM
+ // getBBox() accepts flags, maybe we should strip some of those here?
+
+ // We need to include zero width/height vertical/horizontal lines, so we have
+ // to use UnionEdges.
+ bbox.UnionEdges(child->GetBBoxContribution(tm, aFlags));
+
+ return bbox;
+}
+
+void SVGMarkerFrame::SetParentCoordCtxProvider(SVGViewportElement* aContext) {
+ SVGMarkerElement* marker = static_cast<SVGMarkerElement*>(GetContent());
+ marker->SetParentCoordCtxProvider(aContext);
+}
+
+void SVGMarkerFrame::AppendDirectlyOwnedAnonBoxes(
+ nsTArray<OwnedAnonBox>& aResult) {
+ aResult.AppendElement(OwnedAnonBox(GetAnonymousChildFrame(this)));
+}
+
+//----------------------------------------------------------------------
+// helper class
+
+SVGMarkerFrame::AutoMarkerReferencer::AutoMarkerReferencer(
+ SVGMarkerFrame* aFrame, SVGGeometryFrame* aMarkedFrame)
+ : mFrame(aFrame) {
+ mFrame->mInUse = true;
+ mFrame->mMarkedFrame = aMarkedFrame;
+
+ SVGViewportElement* ctx =
+ static_cast<SVGElement*>(aMarkedFrame->GetContent())->GetCtx();
+ mFrame->SetParentCoordCtxProvider(ctx);
+}
+
+SVGMarkerFrame::AutoMarkerReferencer::~AutoMarkerReferencer() {
+ mFrame->SetParentCoordCtxProvider(nullptr);
+
+ mFrame->mMarkedFrame = nullptr;
+ mFrame->mInUse = false;
+}
+
+} // namespace mozilla
+
+//----------------------------------------------------------------------
+// Implementation of SVGMarkerAnonChildFrame
+
+nsContainerFrame* NS_NewSVGMarkerAnonChildFrame(
+ mozilla::PresShell* aPresShell, mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGMarkerAnonChildFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGMarkerAnonChildFrame)
+
+#ifdef DEBUG
+void SVGMarkerAnonChildFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ MOZ_ASSERT(aParent->IsSVGMarkerFrame(), "Unexpected parent");
+ SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif
+
+} // namespace mozilla
diff --git a/layout/svg/SVGMarkerFrame.h b/layout/svg/SVGMarkerFrame.h
new file mode 100644
index 0000000000..7f4da9c5a5
--- /dev/null
+++ b/layout/svg/SVGMarkerFrame.h
@@ -0,0 +1,164 @@
+/* -*- 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 LAYOUT_SVG_SVGMARKERFRAME_H_
+#define LAYOUT_SVG_SVGMARKERFRAME_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/SVGContainerFrame.h"
+#include "gfxMatrix.h"
+#include "gfxRect.h"
+#include "nsIFrame.h"
+#include "nsLiteralString.h"
+#include "nsQueryFrame.h"
+
+class gfxContext;
+
+namespace mozilla {
+
+class PresShell;
+class SVGGeometryFrame;
+
+struct SVGMark;
+
+namespace dom {
+class SVGViewportElement;
+} // namespace dom
+} // namespace mozilla
+
+nsContainerFrame* NS_NewSVGMarkerFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsContainerFrame* NS_NewSVGMarkerAnonChildFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGMarkerFrame final : public SVGContainerFrame {
+ using imgDrawingParams = image::imgDrawingParams;
+
+ friend class SVGMarkerAnonChildFrame;
+ friend nsContainerFrame* ::NS_NewSVGMarkerFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ protected:
+ explicit SVGMarkerFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : SVGContainerFrame(aStyle, aPresContext, kClassID),
+ mMarkedFrame(nullptr),
+ mInUse(false),
+ mInUse2(false) {
+ AddStateBits(NS_FRAME_IS_NONDISPLAY);
+ }
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGMarkerFrame)
+
+ // nsIFrame interface:
+#ifdef DEBUG
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override {}
+
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGMarker"_ns, aResult);
+ }
+#endif
+
+ nsContainerFrame* GetContentInsertionFrame() override {
+ // Any children must be added to our single anonymous inner frame kid.
+ MOZ_ASSERT(
+ PrincipalChildList().FirstChild() &&
+ PrincipalChildList().FirstChild()->IsSVGMarkerAnonChildFrame(),
+ "Where is our anonymous child?");
+ return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
+ }
+
+ // SVGMarkerFrame methods:
+ void PaintMark(gfxContext& aContext, const gfxMatrix& aToMarkedFrameUserSpace,
+ SVGGeometryFrame* aMarkedFrame, const SVGMark& aMark,
+ float aStrokeWidth, imgDrawingParams& aImgParams);
+
+ SVGBBox GetMarkBBoxContribution(const Matrix& aToBBoxUserspace,
+ uint32_t aFlags,
+ SVGGeometryFrame* aMarkedFrame,
+ const SVGMark& aMark, float aStrokeWidth);
+
+ // Return our anonymous box child.
+ void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override;
+
+ private:
+ // stuff needed for callback
+ SVGGeometryFrame* mMarkedFrame;
+ Matrix mMarkerTM;
+
+ // SVGContainerFrame methods:
+ gfxMatrix GetCanvasTM() override;
+
+ // A helper class to allow us to paint markers safely. The helper
+ // automatically sets and clears the mInUse flag on the marker frame (to
+ // prevent nasty reference loops) as well as the reference to the marked
+ // frame and its coordinate context. It's easy to mess this up
+ // and break things, so this helper makes the code far more robust.
+ class MOZ_RAII AutoMarkerReferencer {
+ public:
+ AutoMarkerReferencer(SVGMarkerFrame* aFrame,
+ SVGGeometryFrame* aMarkedFrame);
+ ~AutoMarkerReferencer();
+
+ private:
+ SVGMarkerFrame* mFrame;
+ };
+
+ // SVGMarkerFrame methods:
+ void SetParentCoordCtxProvider(dom::SVGViewportElement* aContext);
+
+ // recursion prevention flag
+ bool mInUse;
+
+ // second recursion prevention flag, for GetCanvasTM()
+ bool mInUse2;
+};
+
+////////////////////////////////////////////////////////////////////////
+// nsMarkerAnonChildFrame class
+
+class SVGMarkerAnonChildFrame final : public SVGDisplayContainerFrame {
+ friend nsContainerFrame* ::NS_NewSVGMarkerAnonChildFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ explicit SVGMarkerAnonChildFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : SVGDisplayContainerFrame(aStyle, aPresContext, kClassID) {}
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGMarkerAnonChildFrame)
+
+#ifdef DEBUG
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGMarkerAnonChild"_ns, aResult);
+ }
+#endif
+
+ // SVGContainerFrame methods:
+ gfxMatrix GetCanvasTM() override {
+ return static_cast<SVGMarkerFrame*>(GetParent())->GetCanvasTM();
+ }
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGMARKERFRAME_H_
diff --git a/layout/svg/SVGMaskFrame.cpp b/layout/svg/SVGMaskFrame.cpp
new file mode 100644
index 0000000000..de192823d5
--- /dev/null
+++ b/layout/svg/SVGMaskFrame.cpp
@@ -0,0 +1,190 @@
+/* -*- 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:
+#include "SVGMaskFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "AutoReferenceChainGuard.h"
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/SVGMaskElement.h"
+#include "mozilla/dom/SVGUnitTypesBinding.h"
+#include "mozilla/gfx/2D.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::dom::SVGUnitTypes_Binding;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+nsIFrame* NS_NewSVGMaskFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGMaskFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGMaskFrame)
+
+already_AddRefed<SourceSurface> SVGMaskFrame::GetMaskForMaskedFrame(
+ MaskParams& aParams) {
+ // Make sure we break reference loops and over long reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mInUse, &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return nullptr;
+ }
+
+ gfxRect maskArea = GetMaskArea(aParams.maskedFrame);
+ if (maskArea.IsEmpty()) {
+ return nullptr;
+ }
+ // Get the clip extents in device space:
+ // Minimizing the mask surface extents (using both the current clip extents
+ // and maskArea) is important for performance.
+ //
+ gfxRect maskSurfaceRectDouble = aParams.toUserSpace.TransformBounds(maskArea);
+ Rect maskSurfaceRect = ToRect(maskSurfaceRectDouble);
+ maskSurfaceRect.RoundOut();
+
+ StyleMaskType maskType;
+ if (aParams.maskMode == StyleMaskMode::MatchSource) {
+ maskType = StyleSVGReset()->mMaskType;
+ } else {
+ maskType = aParams.maskMode == StyleMaskMode::Luminance
+ ? StyleMaskType::Luminance
+ : StyleMaskType::Alpha;
+ }
+
+ RefPtr<DrawTarget> maskDT;
+ if (maskType == StyleMaskType::Luminance) {
+ maskDT = aParams.dt->CreateClippedDrawTarget(maskSurfaceRect,
+ SurfaceFormat::B8G8R8A8);
+ } else {
+ maskDT =
+ aParams.dt->CreateClippedDrawTarget(maskSurfaceRect, SurfaceFormat::A8);
+ }
+
+ if (!maskDT || !maskDT->IsValid()) {
+ return nullptr;
+ }
+
+ RefPtr<gfxContext> tmpCtx =
+ gfxContext::CreatePreservingTransformOrNull(maskDT);
+ MOZ_ASSERT(tmpCtx); // already checked the draw target above
+
+ mMatrixForChildren =
+ GetMaskTransform(aParams.maskedFrame) * aParams.toUserSpace;
+
+ for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
+ gfxMatrix m = mMatrixForChildren;
+
+ // The CTM of each frame referencing us can be different
+ ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
+ if (SVGFrame) {
+ SVGFrame->NotifySVGChanged(ISVGDisplayableFrame::TRANSFORM_CHANGED);
+ m = SVGUtils::GetTransformMatrixInUserSpace(kid) * m;
+ }
+
+ SVGUtils::PaintFrameWithEffects(kid, *tmpCtx, m, aParams.imgParams);
+ }
+
+ RefPtr<SourceSurface> surface;
+ if (maskType == StyleMaskType::Luminance) {
+ auto luminanceType = LuminanceType::LUMINANCE;
+ if (StyleSVG()->mColorInterpolation == StyleColorInterpolation::Linearrgb) {
+ luminanceType = LuminanceType::LINEARRGB;
+ }
+
+ RefPtr<SourceSurface> maskSnapshot =
+ maskDT->IntoLuminanceSource(luminanceType, aParams.opacity);
+ if (!maskSnapshot) {
+ return nullptr;
+ }
+ surface = std::move(maskSnapshot);
+ } else {
+ maskDT->FillRect(maskSurfaceRect,
+ ColorPattern(DeviceColor::MaskWhite(aParams.opacity)),
+ DrawOptions(1, CompositionOp::OP_IN));
+ RefPtr<SourceSurface> maskSnapshot = maskDT->Snapshot();
+ if (!maskSnapshot) {
+ return nullptr;
+ }
+ surface = std::move(maskSnapshot);
+ }
+
+ return surface.forget();
+}
+
+gfxRect SVGMaskFrame::GetMaskArea(nsIFrame* aMaskedFrame) {
+ SVGMaskElement* maskElem = static_cast<SVGMaskElement*>(GetContent());
+
+ uint16_t units =
+ maskElem->mEnumAttributes[SVGMaskElement::MASKUNITS].GetAnimValue();
+ gfxRect bbox;
+ if (units == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ bbox =
+ SVGUtils::GetBBox(aMaskedFrame, SVGUtils::eUseFrameBoundsForOuterSVG |
+ SVGUtils::eBBoxIncludeFillGeometry);
+ }
+
+ // Bounds in the user space of aMaskedFrame
+ gfxRect maskArea = SVGUtils::GetRelativeRect(
+ units, &maskElem->mLengthAttributes[SVGMaskElement::ATTR_X], bbox,
+ aMaskedFrame);
+
+ return maskArea;
+}
+
+nsresult SVGMaskFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType) {
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y ||
+ aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height ||
+ aAttribute == nsGkAtoms::maskUnits ||
+ aAttribute == nsGkAtoms::maskContentUnits)) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(this);
+ }
+
+ return SVGContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+}
+
+#ifdef DEBUG
+void SVGMaskFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::mask),
+ "Content is not an SVG mask");
+
+ SVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+gfxMatrix SVGMaskFrame::GetCanvasTM() { return mMatrixForChildren; }
+
+gfxMatrix SVGMaskFrame::GetMaskTransform(nsIFrame* aMaskedFrame) {
+ SVGMaskElement* content = static_cast<SVGMaskElement*>(GetContent());
+
+ SVGAnimatedEnumeration* maskContentUnits =
+ &content->mEnumAttributes[SVGMaskElement::MASKCONTENTUNITS];
+
+ uint32_t flags = SVGUtils::eBBoxIncludeFillGeometry |
+ (aMaskedFrame->StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone
+ ? SVGUtils::eIncludeOnlyCurrentFrameForNonSVGElement
+ : 0);
+
+ return SVGUtils::AdjustMatrixForUnits(gfxMatrix(), maskContentUnits,
+ aMaskedFrame, flags);
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGMaskFrame.h b/layout/svg/SVGMaskFrame.h
new file mode 100644
index 0000000000..ee97415459
--- /dev/null
+++ b/layout/svg/SVGMaskFrame.h
@@ -0,0 +1,111 @@
+/* -*- 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 LAYOUT_SVG_SVGMASKFRAME_H_
+#define LAYOUT_SVG_SVGMASKFRAME_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SVGContainerFrame.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxPattern.h"
+#include "gfxMatrix.h"
+
+class gfxContext;
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGMaskFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGMaskFrame final : public SVGContainerFrame {
+ friend nsIFrame* ::NS_NewSVGMaskFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ using Matrix = gfx::Matrix;
+ using SourceSurface = gfx::SourceSurface;
+ using imgDrawingParams = image::imgDrawingParams;
+
+ protected:
+ explicit SVGMaskFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : SVGContainerFrame(aStyle, aPresContext, kClassID), mInUse(false) {
+ AddStateBits(NS_FRAME_IS_NONDISPLAY);
+ }
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGMaskFrame)
+
+ struct MaskParams {
+ gfx::DrawTarget* dt;
+ nsIFrame* maskedFrame;
+ const gfxMatrix& toUserSpace;
+ float opacity;
+ StyleMaskMode maskMode;
+ imgDrawingParams& imgParams;
+
+ explicit MaskParams(gfx::DrawTarget* aDt, nsIFrame* aMaskedFrame,
+ const gfxMatrix& aToUserSpace, float aOpacity,
+ StyleMaskMode aMaskMode, imgDrawingParams& aImgParams)
+ : dt(aDt),
+ maskedFrame(aMaskedFrame),
+ toUserSpace(aToUserSpace),
+ opacity(aOpacity),
+ maskMode(aMaskMode),
+ imgParams(aImgParams) {}
+ };
+
+ // SVGMaskFrame method:
+
+ /**
+ * Generate a mask surface for the target frame.
+ *
+ * The return surface can be null, it's the caller's responsibility to
+ * null-check before dereferencing.
+ */
+ already_AddRefed<SourceSurface> GetMaskForMaskedFrame(MaskParams& aParams);
+
+ gfxRect GetMaskArea(nsIFrame* aMaskedFrame);
+
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+#ifdef DEBUG
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override {}
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGMask"_ns, aResult);
+ }
+#endif
+
+ private:
+ /**
+ * If the mask element transforms its children due to
+ * maskContentUnits="objectBoundingBox" being set on it, this function
+ * returns the resulting transform.
+ */
+ gfxMatrix GetMaskTransform(nsIFrame* aMaskedFrame);
+
+ gfxMatrix mMatrixForChildren;
+ // recursion prevention flag
+ bool mInUse;
+
+ // SVGContainerFrame methods:
+ gfxMatrix GetCanvasTM() override;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGMASKFRAME_H_
diff --git a/layout/svg/SVGObserverUtils.cpp b/layout/svg/SVGObserverUtils.cpp
new file mode 100644
index 0000000000..bd0602ebb8
--- /dev/null
+++ b/layout/svg/SVGObserverUtils.cpp
@@ -0,0 +1,1766 @@
+/* -*- 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:
+#include "SVGObserverUtils.h"
+
+// Keep others in (case-insensitive) order:
+#include "mozilla/css/ImageLoader.h"
+#include "mozilla/dom/CanvasRenderingContext2D.h"
+#include "mozilla/dom/SVGGeometryElement.h"
+#include "mozilla/dom/SVGTextPathElement.h"
+#include "mozilla/dom/SVGUseElement.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/SVGClipPathFrame.h"
+#include "mozilla/SVGMaskFrame.h"
+#include "mozilla/SVGTextFrame.h"
+#include "mozilla/SVGUtils.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsHashKeys.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIReflowCallback.h"
+#include "nsISupportsImpl.h"
+#include "nsLayoutUtils.h"
+#include "nsNetUtil.h"
+#include "nsTHashtable.h"
+#include "nsURIHashKey.h"
+#include "SVGFilterFrame.h"
+#include "SVGMarkerFrame.h"
+#include "SVGPaintServerFrame.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+bool URLAndReferrerInfo::operator==(const URLAndReferrerInfo& aRHS) const {
+ bool uriEqual = false, referrerEqual = false;
+ this->mURI->Equals(aRHS.mURI, &uriEqual);
+ this->mReferrerInfo->Equals(aRHS.mReferrerInfo, &referrerEqual);
+
+ return uriEqual && referrerEqual;
+}
+
+class URLAndReferrerInfoHashKey : public PLDHashEntryHdr {
+ public:
+ using KeyType = const URLAndReferrerInfo*;
+ using KeyTypePointer = const URLAndReferrerInfo*;
+
+ explicit URLAndReferrerInfoHashKey(const URLAndReferrerInfo* aKey) noexcept
+ : mKey(aKey) {
+ MOZ_COUNT_CTOR(URLAndReferrerInfoHashKey);
+ }
+ URLAndReferrerInfoHashKey(URLAndReferrerInfoHashKey&& aToMove) noexcept
+ : PLDHashEntryHdr(std::move(aToMove)), mKey(std::move(aToMove.mKey)) {
+ MOZ_COUNT_CTOR(URLAndReferrerInfoHashKey);
+ }
+ MOZ_COUNTED_DTOR(URLAndReferrerInfoHashKey)
+
+ const URLAndReferrerInfo* GetKey() const { return mKey; }
+
+ bool KeyEquals(const URLAndReferrerInfo* aKey) const {
+ if (!mKey) {
+ return !aKey;
+ }
+ return *mKey == *aKey;
+ }
+
+ static const URLAndReferrerInfo* KeyToPointer(
+ const URLAndReferrerInfo* aKey) {
+ return aKey;
+ }
+
+ static PLDHashNumber HashKey(const URLAndReferrerInfo* aKey) {
+ if (!aKey) {
+ // If the key is null, return hash for empty string.
+ return HashString(""_ns);
+ }
+ nsAutoCString urlSpec, referrerSpec;
+ // nsURIHashKey ignores GetSpec() failures, so we do too:
+ Unused << aKey->GetURI()->GetSpec(urlSpec);
+ return AddToHash(
+ HashString(urlSpec),
+ static_cast<ReferrerInfo*>(aKey->GetReferrerInfo())->Hash());
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ protected:
+ RefPtr<const URLAndReferrerInfo> mKey;
+};
+
+static already_AddRefed<URLAndReferrerInfo> ResolveURLUsingLocalRef(
+ nsIFrame* aFrame, const StyleComputedImageUrl& aURL) {
+ MOZ_ASSERT(aFrame);
+
+ nsCOMPtr<nsIURI> uri = aURL.GetURI();
+
+ if (aURL.IsLocalRef()) {
+ uri = SVGObserverUtils::GetBaseURLForLocalRef(aFrame->GetContent(), uri);
+ uri = aURL.ResolveLocalRef(uri);
+ }
+
+ if (!uri) {
+ return nullptr;
+ }
+
+ RefPtr<URLAndReferrerInfo> info =
+ new URLAndReferrerInfo(uri, aURL.ExtraData());
+ return info.forget();
+}
+
+static already_AddRefed<URLAndReferrerInfo> ResolveURLUsingLocalRef(
+ nsIFrame* aFrame, const nsAString& aURL, nsIReferrerInfo* aReferrerInfo) {
+ MOZ_ASSERT(aFrame);
+
+ nsIContent* content = aFrame->GetContent();
+
+ // Like SVGObserverUtils::GetBaseURLForLocalRef, we want to resolve the
+ // URL against any <use> element shadow tree's source document.
+ //
+ // Unlike GetBaseURLForLocalRef, we are assuming that the URL was specified
+ // directly on mFrame's content (because this ResolveURLUsingLocalRef
+ // overload is used for href="" attributes and not CSS URL values), so there
+ // is no need to check whether the URL was specified / inherited from
+ // outside the shadow tree.
+ nsIURI* base = nullptr;
+ const Encoding* encoding = nullptr;
+ if (SVGUseElement* use = content->GetContainingSVGUseShadowHost()) {
+ base = use->GetSourceDocURI();
+ encoding = use->GetSourceDocCharacterSet();
+ }
+
+ if (!base) {
+ base = content->OwnerDoc()->GetDocumentURI();
+ encoding = content->OwnerDoc()->GetDocumentCharacterSet();
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ Unused << NS_NewURI(getter_AddRefs(uri), aURL, WrapNotNull(encoding), base);
+
+ if (!uri) {
+ return nullptr;
+ }
+
+ RefPtr<URLAndReferrerInfo> info = new URLAndReferrerInfo(uri, aReferrerInfo);
+ return info.forget();
+}
+
+class SVGFilterObserverList;
+
+/**
+ * A class used as a member of the "observer" classes below to help them
+ * avoid dereferencing their frame during presshell teardown when their frame
+ * may have been destroyed (leaving their pointer to their frame dangling).
+ *
+ * When a presshell is torn down, the properties for each frame may not be
+ * deleted until after the frames are destroyed. "Observer" objects (attached
+ * as frame properties) must therefore check whether the presshell is being
+ * torn down before using their pointer to their frame.
+ *
+ * mFramePresShell may be null, but when mFrame is non-null, mFramePresShell
+ * is guaranteed to be non-null, too.
+ */
+struct SVGFrameReferenceFromProperty {
+ explicit SVGFrameReferenceFromProperty(nsIFrame* aFrame)
+ : mFrame(aFrame), mFramePresShell(aFrame->PresShell()) {}
+
+ // Clear our reference to the frame.
+ void Detach() {
+ mFrame = nullptr;
+ mFramePresShell = nullptr;
+ }
+
+ // null if the frame has become invalid
+ nsIFrame* Get() {
+ if (mFramePresShell && mFramePresShell->IsDestroying()) {
+ Detach(); // mFrame is no longer valid.
+ }
+ return mFrame;
+ }
+
+ private:
+ // The frame that our property is attached to (may be null).
+ nsIFrame* mFrame;
+ PresShell* mFramePresShell;
+};
+
+void SVGRenderingObserver::StartObserving() {
+ Element* target = GetReferencedElementWithoutObserving();
+ if (target) {
+ target->AddMutationObserver(this);
+ }
+}
+
+void SVGRenderingObserver::StopObserving() {
+ Element* target = GetReferencedElementWithoutObserving();
+
+ if (target) {
+ target->RemoveMutationObserver(this);
+ if (mInObserverSet) {
+ SVGObserverUtils::RemoveRenderingObserver(target, this);
+ mInObserverSet = false;
+ }
+ }
+ NS_ASSERTION(!mInObserverSet, "still in an observer set?");
+}
+
+Element* SVGRenderingObserver::GetAndObserveReferencedElement() {
+#ifdef DEBUG
+ DebugObserverSet();
+#endif
+ Element* referencedElement = GetReferencedElementWithoutObserving();
+ if (referencedElement && !mInObserverSet) {
+ SVGObserverUtils::AddRenderingObserver(referencedElement, this);
+ mInObserverSet = true;
+ }
+ return referencedElement;
+}
+
+nsIFrame* SVGRenderingObserver::GetAndObserveReferencedFrame() {
+ Element* referencedElement = GetAndObserveReferencedElement();
+ return referencedElement ? referencedElement->GetPrimaryFrame() : nullptr;
+}
+
+nsIFrame* SVGRenderingObserver::GetAndObserveReferencedFrame(
+ LayoutFrameType aFrameType, bool* aOK) {
+ nsIFrame* frame = GetAndObserveReferencedFrame();
+ if (frame) {
+ if (frame->Type() == aFrameType) {
+ return frame;
+ }
+ if (aOK) {
+ *aOK = false;
+ }
+ }
+ return nullptr;
+}
+
+void SVGRenderingObserver::OnNonDOMMutationRenderingChange() {
+ mInObserverSet = false;
+ OnRenderingChange();
+}
+
+void SVGRenderingObserver::NotifyEvictedFromRenderingObserverSet() {
+ mInObserverSet = false; // We've been removed from rendering-obs. set.
+ StopObserving(); // Stop observing mutations too.
+}
+
+void SVGRenderingObserver::AttributeChanged(dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ if (aElement->IsInNativeAnonymousSubtree()) {
+ // Don't observe attribute changes in native-anonymous subtrees like
+ // scrollbars.
+ return;
+ }
+
+ // An attribute belonging to the element that we are observing *or one of its
+ // descendants* has changed.
+ //
+ // In the case of observing a gradient element, say, we want to know if any
+ // of its 'stop' element children change, but we don't actually want to do
+ // anything for changes to SMIL element children, for example. Maybe it's not
+ // worth having logic to optimize for that, but in most cases it could be a
+ // small check?
+ //
+ // XXXjwatt: do we really want to blindly break the link between our
+ // observers and ourselves for all attribute changes? For non-ID changes
+ // surely that is unnecessary.
+
+ OnRenderingChange();
+}
+
+void SVGRenderingObserver::ContentAppended(nsIContent* aFirstNewContent) {
+ OnRenderingChange();
+}
+
+void SVGRenderingObserver::ContentInserted(nsIContent* aChild) {
+ OnRenderingChange();
+}
+
+void SVGRenderingObserver::ContentRemoved(nsIContent* aChild,
+ nsIContent* aPreviousSibling) {
+ OnRenderingChange();
+}
+
+/**
+ * SVG elements reference supporting resources by element ID. We need to
+ * track when those resources change and when the document changes in ways
+ * that affect which element is referenced by a given ID (e.g., when
+ * element IDs change). The code here is responsible for that.
+ *
+ * When a frame references a supporting resource, we create a property
+ * object derived from SVGIDRenderingObserver to manage the relationship. The
+ * property object is attached to the referencing frame.
+ */
+class SVGIDRenderingObserver : public SVGRenderingObserver {
+ public:
+ // Callback for checking if the element being observed is valid for this
+ // observer. Note that this may be called during construction, before the
+ // deriving class is fully constructed.
+ using TargetIsValidCallback = bool (*)(const Element&);
+ SVGIDRenderingObserver(
+ URLAndReferrerInfo* aURI, nsIContent* aObservingContent,
+ bool aReferenceImage,
+ TargetIsValidCallback aTargetIsValidCallback = nullptr);
+
+ protected:
+ virtual ~SVGIDRenderingObserver() {
+ // This needs to call our GetReferencedElementWithoutObserving override,
+ // so must be called here rather than in our base class's dtor.
+ StopObserving();
+ }
+
+ void TargetChanged() {
+ mTargetIsValid = ([this] {
+ Element* observed = mObservedElementTracker.get();
+ if (!observed) {
+ return false;
+ }
+ // If the content is observing an ancestor, then return the target is not
+ // valid.
+ //
+ // TODO(emilio): Should we allow content observing its own descendants?
+ // That seems potentially-bad as well.
+ if (observed->OwnerDoc() == mObservingContent->OwnerDoc() &&
+ nsContentUtils::ContentIsHostIncludingDescendantOf(mObservingContent,
+ observed)) {
+ return false;
+ }
+ if (mTargetIsValidCallback) {
+ return mTargetIsValidCallback(*observed);
+ }
+ return true;
+ }());
+ }
+
+ Element* GetReferencedElementWithoutObserving() final {
+ return mTargetIsValid ? mObservedElementTracker.get() : nullptr;
+ }
+
+ void OnRenderingChange() override;
+
+ /**
+ * Helper that provides a reference to the element with the ID that our
+ * observer wants to observe, and that will invalidate our observer if the
+ * element that that ID identifies changes to a different element (or none).
+ */
+ class ElementTracker final : public IDTracker {
+ public:
+ explicit ElementTracker(SVGIDRenderingObserver* aOwningObserver)
+ : mOwningObserver(aOwningObserver) {}
+
+ protected:
+ void ElementChanged(Element* aFrom, Element* aTo) override {
+ // Call OnRenderingChange() before the target changes, so that
+ // mIsTargetValid reflects the right state.
+ mOwningObserver->OnRenderingChange();
+ mOwningObserver->StopObserving();
+ IDTracker::ElementChanged(aFrom, aTo);
+ mOwningObserver->TargetChanged();
+ mOwningObserver->StartObserving();
+ // And same after the target changes, for the same reason.
+ mOwningObserver->OnRenderingChange();
+ }
+ /**
+ * Override IsPersistent because we want to keep tracking the element
+ * for the ID even when it changes.
+ */
+ bool IsPersistent() override { return true; }
+
+ private:
+ SVGIDRenderingObserver* mOwningObserver;
+ };
+
+ ElementTracker mObservedElementTracker;
+ RefPtr<Element> mObservingContent;
+ bool mTargetIsValid = false;
+ TargetIsValidCallback mTargetIsValidCallback;
+};
+
+/**
+ * Note that in the current setup there are two separate observer lists.
+ *
+ * In SVGIDRenderingObserver's ctor, the new object adds itself to the
+ * mutation observer list maintained by the referenced element. In this way the
+ * SVGIDRenderingObserver is notified if there are any attribute or content
+ * tree changes to the element or any of its *descendants*.
+ *
+ * In SVGIDRenderingObserver::GetAndObserveReferencedElement() the
+ * SVGIDRenderingObserver object also adds itself to an
+ * SVGRenderingObserverSet object belonging to the referenced
+ * element.
+ *
+ * XXX: it would be nice to have a clear and concise executive summary of the
+ * benefits/necessity of maintaining a second observer list.
+ */
+SVGIDRenderingObserver::SVGIDRenderingObserver(
+ URLAndReferrerInfo* aURI, nsIContent* aObservingContent,
+ bool aReferenceImage, TargetIsValidCallback aTargetIsValidCallback)
+ : mObservedElementTracker(this),
+ mObservingContent(aObservingContent->AsElement()),
+ mTargetIsValidCallback(aTargetIsValidCallback) {
+ // Start watching the target element
+ nsIURI* uri = nullptr;
+ nsIReferrerInfo* referrerInfo = nullptr;
+ if (aURI) {
+ uri = aURI->GetURI();
+ referrerInfo = aURI->GetReferrerInfo();
+ }
+
+ mObservedElementTracker.ResetToURIFragmentID(
+ aObservingContent, uri, referrerInfo, true, aReferenceImage);
+ TargetChanged();
+ StartObserving();
+}
+
+void SVGIDRenderingObserver::OnRenderingChange() {
+ if (mObservedElementTracker.get() && mInObserverSet) {
+ SVGObserverUtils::RemoveRenderingObserver(mObservedElementTracker.get(),
+ this);
+ mInObserverSet = false;
+ }
+}
+
+class SVGRenderingObserverProperty : public SVGIDRenderingObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ SVGRenderingObserverProperty(URLAndReferrerInfo* aURI, nsIFrame* aFrame,
+ bool aReferenceImage)
+ : SVGIDRenderingObserver(aURI, aFrame->GetContent(), aReferenceImage),
+ mFrameReference(aFrame) {}
+
+ protected:
+ virtual ~SVGRenderingObserverProperty() = default; // non-public
+
+ void OnRenderingChange() override;
+
+ SVGFrameReferenceFromProperty mFrameReference;
+};
+
+NS_IMPL_ISUPPORTS(SVGRenderingObserverProperty, nsIMutationObserver)
+
+void SVGRenderingObserverProperty::OnRenderingChange() {
+ SVGIDRenderingObserver::OnRenderingChange();
+
+ if (!mTargetIsValid) {
+ return;
+ }
+
+ nsIFrame* frame = mFrameReference.Get();
+
+ if (frame && frame->HasAllStateBits(NS_FRAME_SVG_LAYOUT)) {
+ // We need to notify anything that is observing the referencing frame or
+ // any of its ancestors that the referencing frame has been invalidated.
+ // Since walking the parent chain checking for observers is expensive we
+ // do that using a change hint (multiple change hints of the same type are
+ // coalesced).
+ nsLayoutUtils::PostRestyleEvent(frame->GetContent()->AsElement(),
+ RestyleHint{0},
+ nsChangeHint_InvalidateRenderingObservers);
+ }
+}
+
+class SVGTextPathObserver final : public SVGRenderingObserverProperty {
+ public:
+ SVGTextPathObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame,
+ bool aReferenceImage)
+ : SVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) {}
+
+ protected:
+ void OnRenderingChange() override;
+};
+
+void SVGTextPathObserver::OnRenderingChange() {
+ SVGRenderingObserverProperty::OnRenderingChange();
+
+ if (!mTargetIsValid) {
+ return;
+ }
+
+ nsIFrame* frame = mFrameReference.Get();
+ if (!frame) {
+ return;
+ }
+
+ MOZ_ASSERT(frame->IsFrameOfType(nsIFrame::eSVG) ||
+ SVGUtils::IsInSVGTextSubtree(frame),
+ "SVG frame expected");
+
+ MOZ_ASSERT(frame->GetContent()->IsSVGElement(nsGkAtoms::textPath),
+ "expected frame for a <textPath> element");
+
+ auto* text = static_cast<SVGTextFrame*>(
+ nsLayoutUtils::GetClosestFrameOfType(frame, LayoutFrameType::SVGText));
+ MOZ_ASSERT(text, "expected to find an ancestor SVGTextFrame");
+ if (text) {
+ text->AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY |
+ NS_STATE_SVG_POSITIONING_DIRTY);
+
+ if (SVGUtils::AnyOuterSVGIsCallingReflowSVG(text)) {
+ text->AddStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
+ if (text->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ text->ReflowSVGNonDisplayText();
+ } else {
+ text->ReflowSVG();
+ }
+ } else {
+ text->ScheduleReflowSVG();
+ }
+ }
+}
+
+class SVGMarkerObserver final : public SVGRenderingObserverProperty {
+ public:
+ SVGMarkerObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame,
+ bool aReferenceImage)
+ : SVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) {}
+
+ protected:
+ void OnRenderingChange() override;
+};
+
+void SVGMarkerObserver::OnRenderingChange() {
+ SVGRenderingObserverProperty::OnRenderingChange();
+
+ nsIFrame* frame = mFrameReference.Get();
+ if (!frame) {
+ return;
+ }
+
+ MOZ_ASSERT(frame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected");
+
+ // Don't need to request ReflowFrame if we're being reflowed.
+ // Because mRect for SVG frames includes the bounds of any markers
+ // (see the comment for nsIFrame::GetRect), the referencing frame must be
+ // reflowed for any marker changes.
+ if (!frame->HasAnyStateBits(NS_FRAME_IN_REFLOW)) {
+ // XXXjwatt: We need to unify SVG into standard reflow so we can just use
+ // nsChangeHint_NeedReflow | nsChangeHint_NeedDirtyReflow here.
+ // XXXSDL KILL THIS!!!
+ SVGUtils::ScheduleReflowSVG(frame);
+ }
+ frame->PresContext()->RestyleManager()->PostRestyleEvent(
+ frame->GetContent()->AsElement(), RestyleHint{0},
+ nsChangeHint_RepaintFrame);
+}
+
+class SVGPaintingProperty : public SVGRenderingObserverProperty {
+ public:
+ SVGPaintingProperty(URLAndReferrerInfo* aURI, nsIFrame* aFrame,
+ bool aReferenceImage)
+ : SVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) {}
+
+ protected:
+ void OnRenderingChange() override;
+};
+
+void SVGPaintingProperty::OnRenderingChange() {
+ SVGRenderingObserverProperty::OnRenderingChange();
+
+ nsIFrame* frame = mFrameReference.Get();
+ if (!frame) {
+ return;
+ }
+
+ if (frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ frame->InvalidateFrameSubtree();
+ } else {
+ for (nsIFrame* f = frame; f;
+ f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
+ f->InvalidateFrame();
+ }
+ }
+}
+
+// Observer for -moz-element(#element). Note that the observed element does not
+// have to be an SVG element.
+class SVGMozElementObserver final : public SVGPaintingProperty {
+ public:
+ SVGMozElementObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame,
+ bool aReferenceImage)
+ : SVGPaintingProperty(aURI, aFrame, aReferenceImage) {}
+
+ // We only return true here because GetAndObserveBackgroundImage uses us
+ // to implement observing of arbitrary elements (including HTML elements)
+ // that may require us to repaint if the referenced element is reflowed.
+ // Bug 1496065 has been filed to remove that support though.
+ bool ObservesReflow() override { return true; }
+};
+
+/**
+ * For content with `background-clip: text`.
+ *
+ * This observer is unusual in that the observing frame and observed frame are
+ * the same frame. This is because the observing frame is observing for reflow
+ * of its descendant text nodes, since such reflows may not result in the
+ * frame's nsDisplayBackground changing. In other words, Display List Based
+ * Invalidation may not invalidate the frame's background, so we need this
+ * observer to make sure that happens.
+ *
+ * XXX: It's questionable whether we should even be [ab]using the SVG observer
+ * mechanism for `background-clip:text`. Since we know that the observed frame
+ * is the frame we need to invalidate, we could just check the computed style
+ * in the (one) place where we pass INVALIDATE_REFLOW and invalidate there...
+ */
+class BackgroundClipRenderingObserver : public SVGRenderingObserver {
+ public:
+ explicit BackgroundClipRenderingObserver(nsIFrame* aFrame) : mFrame(aFrame) {}
+
+ NS_DECL_ISUPPORTS
+
+ private:
+ // We do not call StopObserving() since the observing and observed element
+ // are the same element (and because we could crash - see bug 1556441).
+ virtual ~BackgroundClipRenderingObserver() = default;
+
+ Element* GetReferencedElementWithoutObserving() final {
+ return mFrame->GetContent()->AsElement();
+ }
+
+ void OnRenderingChange() final;
+
+ /**
+ * Observing for mutations is not enough. A new font loading and applying
+ * to the text content could cause it to reflow, and we need to invalidate
+ * for that.
+ */
+ bool ObservesReflow() final { return true; }
+
+ // The observer and observee!
+ nsIFrame* mFrame;
+};
+
+NS_IMPL_ISUPPORTS(BackgroundClipRenderingObserver, nsIMutationObserver)
+
+void BackgroundClipRenderingObserver::OnRenderingChange() {
+ for (nsIFrame* f = mFrame; f;
+ f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
+ f->InvalidateFrame();
+ }
+}
+
+static bool IsSVGFilterElement(const Element& aObserved) {
+ return aObserved.IsSVGElement(nsGkAtoms::filter);
+}
+
+/**
+ * In a filter chain, there can be multiple SVG reference filters.
+ * e.g. filter: url(#svg-filter-1) blur(10px) url(#svg-filter-2);
+ *
+ * This class keeps track of one SVG reference filter in a filter chain.
+ * e.g. url(#svg-filter-1)
+ *
+ * It fires invalidations when the SVG filter element's id changes or when
+ * the SVG filter element's content changes.
+ *
+ * The SVGFilterObserverList class manages a list of SVGFilterObservers.
+ */
+class SVGFilterObserver final : public SVGIDRenderingObserver {
+ public:
+ SVGFilterObserver(URLAndReferrerInfo* aURI, nsIContent* aObservingContent,
+ SVGFilterObserverList* aFilterChainObserver)
+ : SVGIDRenderingObserver(aURI, aObservingContent, false,
+ IsSVGFilterElement),
+ mFilterObserverList(aFilterChainObserver) {}
+
+ void DetachFromChainObserver() { mFilterObserverList = nullptr; }
+
+ /**
+ * @return the filter frame, or null if there is no filter frame
+ */
+ SVGFilterFrame* GetAndObserveFilterFrame();
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(SVGFilterObserver)
+
+ // SVGIDRenderingObserver
+ void OnRenderingChange() override;
+
+ protected:
+ virtual ~SVGFilterObserver() = default; // non-public
+
+ SVGFilterObserverList* mFilterObserverList;
+};
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SVGFilterObserver)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(SVGFilterObserver)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGFilterObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(SVGFilterObserver)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SVGFilterObserver)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservedElementTracker)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservingContent)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SVGFilterObserver)
+ tmp->StopObserving();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservedElementTracker);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservingContent)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+SVGFilterFrame* SVGFilterObserver::GetAndObserveFilterFrame() {
+ return static_cast<SVGFilterFrame*>(
+ GetAndObserveReferencedFrame(LayoutFrameType::SVGFilter, nullptr));
+}
+
+/**
+ * This class manages a list of SVGFilterObservers, which correspond to
+ * reference to SVG filters in a list of filters in a given 'filter' property.
+ * e.g. filter: url(#svg-filter-1) blur(10px) url(#svg-filter-2);
+ *
+ * In the above example, the SVGFilterObserverList will manage two
+ * SVGFilterObservers, one for each of the references to SVG filters. CSS
+ * filters like "blur(10px)" don't reference filter elements, so they don't
+ * need an SVGFilterObserver. The style system invalidates changes to CSS
+ * filters.
+ *
+ * FIXME(emilio): Why do we need this as opposed to the individual observers we
+ * create in the constructor?
+ */
+class SVGFilterObserverList : public nsISupports {
+ public:
+ SVGFilterObserverList(Span<const StyleFilter> aFilters,
+ nsIContent* aFilteredElement,
+ nsIFrame* aFilteredFrame = nullptr);
+
+ const nsTArray<RefPtr<SVGFilterObserver>>& GetObservers() const {
+ return mObservers;
+ }
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(SVGFilterObserverList)
+
+ virtual void OnRenderingChange() = 0;
+
+ protected:
+ virtual ~SVGFilterObserverList();
+
+ void DetachObservers() {
+ for (auto& observer : mObservers) {
+ observer->DetachFromChainObserver();
+ }
+ }
+
+ nsTArray<RefPtr<SVGFilterObserver>> mObservers;
+};
+
+void SVGFilterObserver::OnRenderingChange() {
+ SVGIDRenderingObserver::OnRenderingChange();
+
+ if (mFilterObserverList) {
+ mFilterObserverList->OnRenderingChange();
+ }
+
+ if (!mTargetIsValid) {
+ return;
+ }
+
+ nsIFrame* frame = mObservingContent->GetPrimaryFrame();
+ if (!frame) {
+ return;
+ }
+
+ // Repaint asynchronously in case the filter frame is being torn down
+ nsChangeHint changeHint = nsChangeHint(nsChangeHint_RepaintFrame);
+
+ // Since we don't call SVGRenderingObserverProperty::
+ // OnRenderingChange, we have to add this bit ourselves.
+ if (frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ // Changes should propagate out to things that might be observing
+ // the referencing frame or its ancestors.
+ changeHint |= nsChangeHint_InvalidateRenderingObservers;
+ }
+
+ // Don't need to request UpdateOverflow if we're being reflowed.
+ if (!frame->HasAnyStateBits(NS_FRAME_IN_REFLOW)) {
+ changeHint |= nsChangeHint_UpdateOverflow;
+ }
+ frame->PresContext()->RestyleManager()->PostRestyleEvent(
+ mObservingContent, RestyleHint{0}, changeHint);
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SVGFilterObserverList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(SVGFilterObserverList)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(SVGFilterObserverList)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SVGFilterObserverList)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservers)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SVGFilterObserverList)
+ tmp->DetachObservers();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGFilterObserverList)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+SVGFilterObserverList::SVGFilterObserverList(Span<const StyleFilter> aFilters,
+ nsIContent* aFilteredElement,
+ nsIFrame* aFilteredFrame) {
+ for (const auto& filter : aFilters) {
+ if (!filter.IsUrl()) {
+ continue;
+ }
+
+ const auto& url = filter.AsUrl();
+
+ // aFilteredFrame can be null if this filter belongs to a
+ // CanvasRenderingContext2D.
+ RefPtr<URLAndReferrerInfo> filterURL;
+ if (aFilteredFrame) {
+ filterURL = ResolveURLUsingLocalRef(aFilteredFrame, url);
+ } else {
+ nsCOMPtr<nsIURI> resolvedURI = url.ResolveLocalRef(aFilteredElement);
+ if (resolvedURI) {
+ filterURL = new URLAndReferrerInfo(resolvedURI, url.ExtraData());
+ }
+ }
+
+ RefPtr<SVGFilterObserver> observer =
+ new SVGFilterObserver(filterURL, aFilteredElement, this);
+ mObservers.AppendElement(observer);
+ }
+}
+
+SVGFilterObserverList::~SVGFilterObserverList() { DetachObservers(); }
+
+class SVGFilterObserverListForCSSProp final : public SVGFilterObserverList {
+ public:
+ SVGFilterObserverListForCSSProp(Span<const StyleFilter> aFilters,
+ nsIFrame* aFilteredFrame)
+ : SVGFilterObserverList(aFilters, aFilteredFrame->GetContent(),
+ aFilteredFrame) {}
+
+ protected:
+ void OnRenderingChange() override;
+ bool mInvalidating = false;
+};
+
+void SVGFilterObserverListForCSSProp::OnRenderingChange() {
+ if (mInvalidating) {
+ return;
+ }
+ AutoRestore<bool> guard(mInvalidating);
+ mInvalidating = true;
+ for (auto& observer : mObservers) {
+ observer->OnRenderingChange();
+ }
+}
+
+class SVGFilterObserverListForCanvasContext final
+ : public SVGFilterObserverList {
+ public:
+ SVGFilterObserverListForCanvasContext(CanvasRenderingContext2D* aContext,
+ Element* aCanvasElement,
+ Span<const StyleFilter> aFilters)
+ : SVGFilterObserverList(aFilters, aCanvasElement), mContext(aContext) {}
+
+ void OnRenderingChange() override;
+ void DetachFromContext() { mContext = nullptr; }
+
+ private:
+ CanvasRenderingContext2D* mContext;
+};
+
+void SVGFilterObserverListForCanvasContext::OnRenderingChange() {
+ if (!mContext) {
+ NS_WARNING(
+ "GFX: This should never be called without a context, except during "
+ "cycle collection (when DetachFromContext has been called)");
+ return;
+ }
+ // Refresh the cached FilterDescription in mContext->CurrentState().filter.
+ // If this filter is not at the top of the state stack, we'll refresh the
+ // wrong filter, but that's ok, because we'll refresh the right filter
+ // when we pop the state stack in CanvasRenderingContext2D::Restore().
+ RefPtr<CanvasRenderingContext2D> kungFuDeathGrip(mContext);
+ kungFuDeathGrip->UpdateFilter();
+}
+
+class SVGMaskObserverList final : public nsISupports {
+ public:
+ explicit SVGMaskObserverList(nsIFrame* aFrame);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ const nsTArray<RefPtr<SVGPaintingProperty>>& GetObservers() const {
+ return mProperties;
+ }
+
+ void ResolveImage(uint32_t aIndex);
+
+ private:
+ virtual ~SVGMaskObserverList() = default; // non-public
+ nsTArray<RefPtr<SVGPaintingProperty>> mProperties;
+ nsIFrame* mFrame;
+};
+
+NS_IMPL_ISUPPORTS(SVGMaskObserverList, nsISupports)
+
+SVGMaskObserverList::SVGMaskObserverList(nsIFrame* aFrame) : mFrame(aFrame) {
+ const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset();
+
+ for (uint32_t i = 0; i < svgReset->mMask.mImageCount; i++) {
+ const StyleComputedImageUrl* data =
+ svgReset->mMask.mLayers[i].mImage.GetImageRequestURLValue();
+ RefPtr<URLAndReferrerInfo> maskUri;
+ if (data) {
+ maskUri = ResolveURLUsingLocalRef(aFrame, *data);
+ }
+
+ bool hasRef = false;
+ if (maskUri) {
+ maskUri->GetURI()->GetHasRef(&hasRef);
+ }
+
+ // Accrording to maskUri, SVGPaintingProperty's ctor may trigger an
+ // external SVG resource download, so we should pass maskUri in only if
+ // maskUri has a chance pointing to an SVG mask resource.
+ //
+ // And, an URL may refer to an SVG mask resource if it consists of
+ // a fragment.
+ SVGPaintingProperty* prop = new SVGPaintingProperty(
+ hasRef ? maskUri.get() : nullptr, aFrame, false);
+ mProperties.AppendElement(prop);
+ }
+}
+
+void SVGMaskObserverList::ResolveImage(uint32_t aIndex) {
+ const nsStyleSVGReset* svgReset = mFrame->StyleSVGReset();
+ MOZ_ASSERT(aIndex < svgReset->mMask.mImageCount);
+
+ auto& image = const_cast<StyleImage&>(svgReset->mMask.mLayers[aIndex].mImage);
+ if (image.IsResolved()) {
+ return;
+ }
+ MOZ_ASSERT(image.IsImageRequestType());
+ Document* doc = mFrame->PresContext()->Document();
+ image.ResolveImage(*doc, nullptr);
+ if (imgRequestProxy* req = image.GetImageRequest()) {
+ // FIXME(emilio): What disassociates this request?
+ doc->StyleImageLoader()->AssociateRequestToFrame(req, mFrame);
+ }
+}
+
+/**
+ * Used for gradient-to-gradient, pattern-to-pattern and filter-to-filter
+ * references to "template" elements (specified via the 'href' attributes).
+ *
+ * This is a special class for the case where we know we only want to call
+ * InvalidateDirectRenderingObservers (as opposed to
+ * InvalidateRenderingObservers).
+ *
+ * TODO(jwatt): If we added a new NS_FRAME_RENDERING_OBSERVER_CONTAINER state
+ * bit to clipPath, filter, gradients, marker, mask, pattern and symbol, and
+ * could have InvalidateRenderingObservers stop on reaching such an element,
+ * then we would no longer need this class (not to mention improving perf by
+ * significantly cutting down on ancestor traversal).
+ */
+class SVGTemplateElementObserver : public SVGIDRenderingObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ SVGTemplateElementObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame,
+ bool aReferenceImage)
+ : SVGIDRenderingObserver(aURI, aFrame->GetContent(), aReferenceImage),
+ mFrameReference(aFrame) {}
+
+ protected:
+ virtual ~SVGTemplateElementObserver() = default; // non-public
+
+ void OnRenderingChange() override;
+
+ SVGFrameReferenceFromProperty mFrameReference;
+};
+
+NS_IMPL_ISUPPORTS(SVGTemplateElementObserver, nsIMutationObserver)
+
+void SVGTemplateElementObserver::OnRenderingChange() {
+ SVGIDRenderingObserver::OnRenderingChange();
+
+ if (nsIFrame* frame = mFrameReference.Get()) {
+ // We know that we don't need to walk the parent chain notifying rendering
+ // observers since changes to a gradient etc. do not affect ancestor
+ // elements. So we only invalidate *direct* rendering observers here.
+ // Since we don't need to walk the parent chain, we don't need to worry
+ // about coalescing multiple invalidations by using a change hint as we do
+ // in SVGRenderingObserverProperty::OnRenderingChange.
+ SVGObserverUtils::InvalidateDirectRenderingObservers(frame);
+ }
+}
+
+/**
+ * An instance of this class is stored on an observed frame (as a frame
+ * property) whenever the frame has active rendering observers. It is used to
+ * store pointers to the SVGRenderingObserver instances belonging to any
+ * observing frames, allowing invalidations from the observed frame to be sent
+ * to all observing frames.
+ *
+ * SVGRenderingObserver instances that are added are not strongly referenced,
+ * so they must remove themselves before they die.
+ *
+ * This class is "single-shot", which is to say that when something about the
+ * observed element changes, InvalidateAll() clears our hashtable of
+ * SVGRenderingObservers. SVGRenderingObserver objects will be added back
+ * again if/when the observing frame looks up our observed frame to use it.
+ *
+ * XXXjwatt: is this the best thing to do nowadays? Back when that mechanism
+ * landed in bug 330498 we had two pass, recursive invalidation up the frame
+ * tree, and I think reference loops were a problem. Nowadays maybe a flag
+ * on the SVGRenderingObserver objects to coalesce invalidations may work
+ * better?
+ *
+ * InvalidateAll must be called before this object is destroyed, i.e.
+ * before the referenced frame is destroyed. This should normally happen
+ * via SVGContainerFrame::RemoveFrame, since only frames in the frame
+ * tree should be referenced.
+ */
+class SVGRenderingObserverSet {
+ public:
+ SVGRenderingObserverSet() : mObservers(4) {
+ MOZ_COUNT_CTOR(SVGRenderingObserverSet);
+ }
+
+ ~SVGRenderingObserverSet() {
+ InvalidateAll();
+ MOZ_COUNT_DTOR(SVGRenderingObserverSet);
+ }
+
+ void Add(SVGRenderingObserver* aObserver) { mObservers.Insert(aObserver); }
+ void Remove(SVGRenderingObserver* aObserver) { mObservers.Remove(aObserver); }
+#ifdef DEBUG
+ bool Contains(SVGRenderingObserver* aObserver) {
+ return mObservers.Contains(aObserver);
+ }
+#endif
+ bool IsEmpty() { return mObservers.IsEmpty(); }
+
+ /**
+ * Drop all our observers, and notify them that we have changed and dropped
+ * our reference to them.
+ */
+ void InvalidateAll();
+
+ /**
+ * Drop all observers that observe reflow, and notify them that we have
+ * changed and dropped our reference to them.
+ */
+ void InvalidateAllForReflow();
+
+ /**
+ * Drop all our observers, and notify them that we have dropped our reference
+ * to them.
+ */
+ void RemoveAll();
+
+ private:
+ nsTHashSet<SVGRenderingObserver*> mObservers;
+};
+
+void SVGRenderingObserverSet::InvalidateAll() {
+ if (mObservers.IsEmpty()) {
+ return;
+ }
+
+ const auto observers = std::move(mObservers);
+
+ for (const auto& observer : observers) {
+ observer->OnNonDOMMutationRenderingChange();
+ }
+}
+
+void SVGRenderingObserverSet::InvalidateAllForReflow() {
+ if (mObservers.IsEmpty()) {
+ return;
+ }
+
+ AutoTArray<SVGRenderingObserver*, 10> observers;
+
+ for (auto it = mObservers.cbegin(), end = mObservers.cend(); it != end;
+ ++it) {
+ SVGRenderingObserver* obs = *it;
+ if (obs->ObservesReflow()) {
+ observers.AppendElement(obs);
+ mObservers.Remove(it);
+ }
+ }
+
+ for (uint32_t i = 0; i < observers.Length(); ++i) {
+ observers[i]->OnNonDOMMutationRenderingChange();
+ }
+}
+
+void SVGRenderingObserverSet::RemoveAll() {
+ const auto observers = std::move(mObservers);
+
+ // Our list is now cleared. We need to notify the observers we've removed,
+ // so they can update their state & remove themselves as mutation-observers.
+ for (const auto& observer : observers) {
+ observer->NotifyEvictedFromRenderingObserverSet();
+ }
+}
+
+static SVGRenderingObserverSet* GetObserverSet(Element* aElement) {
+ return static_cast<SVGRenderingObserverSet*>(
+ aElement->GetProperty(nsGkAtoms::renderingobserverset));
+}
+
+#ifdef DEBUG
+// Defined down here because we need SVGRenderingObserverSet's definition.
+void SVGRenderingObserver::DebugObserverSet() {
+ Element* referencedElement = GetReferencedElementWithoutObserving();
+ if (referencedElement) {
+ SVGRenderingObserverSet* observers = GetObserverSet(referencedElement);
+ bool inObserverSet = observers && observers->Contains(this);
+ MOZ_ASSERT(inObserverSet == mInObserverSet,
+ "failed to track whether we're in our referenced element's "
+ "observer set!");
+ } else {
+ MOZ_ASSERT(!mInObserverSet, "In whose observer set are we, then?");
+ }
+}
+#endif
+
+using URIObserverHashtable =
+ nsInterfaceHashtable<URLAndReferrerInfoHashKey, nsIMutationObserver>;
+
+using PaintingPropertyDescriptor =
+ const FramePropertyDescriptor<SVGPaintingProperty>*;
+
+static void DestroyFilterProperty(SVGFilterObserverListForCSSProp* aProp) {
+ aProp->Release();
+}
+
+NS_DECLARE_FRAME_PROPERTY_RELEASABLE(HrefToTemplateProperty,
+ SVGTemplateElementObserver)
+NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(FilterProperty,
+ SVGFilterObserverListForCSSProp,
+ DestroyFilterProperty)
+NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MaskProperty, SVGMaskObserverList)
+NS_DECLARE_FRAME_PROPERTY_RELEASABLE(ClipPathProperty, SVGPaintingProperty)
+NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerStartProperty, SVGMarkerObserver)
+NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerMidProperty, SVGMarkerObserver)
+NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerEndProperty, SVGMarkerObserver)
+NS_DECLARE_FRAME_PROPERTY_RELEASABLE(FillProperty, SVGPaintingProperty)
+NS_DECLARE_FRAME_PROPERTY_RELEASABLE(StrokeProperty, SVGPaintingProperty)
+NS_DECLARE_FRAME_PROPERTY_RELEASABLE(HrefAsTextPathProperty,
+ SVGTextPathObserver)
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(BackgroundImageProperty,
+ URIObserverHashtable)
+NS_DECLARE_FRAME_PROPERTY_RELEASABLE(BackgroundClipObserverProperty,
+ BackgroundClipRenderingObserver)
+
+template <class T>
+static T* GetEffectProperty(URLAndReferrerInfo* aURI, nsIFrame* aFrame,
+ const FramePropertyDescriptor<T>* aProperty) {
+ if (!aURI) {
+ return nullptr;
+ }
+
+ bool found;
+ T* prop = aFrame->GetProperty(aProperty, &found);
+ if (found) {
+ MOZ_ASSERT(prop, "this property should only store non-null values");
+ return prop;
+ }
+ prop = new T(aURI, aFrame, false);
+ NS_ADDREF(prop);
+ aFrame->AddProperty(aProperty, prop);
+ return prop;
+}
+
+static SVGPaintingProperty* GetPaintingProperty(
+ URLAndReferrerInfo* aURI, nsIFrame* aFrame,
+ const FramePropertyDescriptor<SVGPaintingProperty>* aProperty) {
+ return GetEffectProperty(aURI, aFrame, aProperty);
+}
+
+static already_AddRefed<URLAndReferrerInfo> GetMarkerURI(
+ nsIFrame* aFrame, const StyleUrlOrNone nsStyleSVG::*aMarker) {
+ const StyleUrlOrNone& url = aFrame->StyleSVG()->*aMarker;
+ if (url.IsNone()) {
+ return nullptr;
+ }
+ return ResolveURLUsingLocalRef(aFrame, url.AsUrl());
+}
+
+bool SVGObserverUtils::GetAndObserveMarkers(nsIFrame* aMarkedFrame,
+ SVGMarkerFrame* (*aFrames)[3]) {
+ MOZ_ASSERT(!aMarkedFrame->GetPrevContinuation() &&
+ aMarkedFrame->IsSVGGeometryFrame() &&
+ static_cast<SVGGeometryElement*>(aMarkedFrame->GetContent())
+ ->IsMarkable(),
+ "Bad frame");
+
+ bool foundMarker = false;
+ RefPtr<URLAndReferrerInfo> markerURL;
+ SVGMarkerObserver* observer;
+ nsIFrame* marker;
+
+#define GET_MARKER(type) \
+ markerURL = GetMarkerURI(aMarkedFrame, &nsStyleSVG::mMarker##type); \
+ observer = \
+ GetEffectProperty(markerURL, aMarkedFrame, Marker##type##Property()); \
+ marker = observer ? observer->GetAndObserveReferencedFrame( \
+ LayoutFrameType::SVGMarker, nullptr) \
+ : nullptr; \
+ foundMarker = foundMarker || bool(marker); \
+ (*aFrames)[SVGMark::e##type] = static_cast<SVGMarkerFrame*>(marker);
+
+ GET_MARKER(Start)
+ GET_MARKER(Mid)
+ GET_MARKER(End)
+
+#undef GET_MARKER
+
+ return foundMarker;
+}
+
+// Note that the returned list will be empty in the case of a 'filter' property
+// that only specifies CSS filter functions (no url()'s to SVG filters).
+static SVGFilterObserverListForCSSProp* GetOrCreateFilterObserverListForCSS(
+ nsIFrame* aFrame) {
+ MOZ_ASSERT(!aFrame->GetPrevContinuation(), "Require first continuation");
+
+ const nsStyleEffects* effects = aFrame->StyleEffects();
+ if (!effects->HasFilters()) {
+ return nullptr;
+ }
+
+ bool found;
+ SVGFilterObserverListForCSSProp* observers =
+ aFrame->GetProperty(FilterProperty(), &found);
+ if (found) {
+ MOZ_ASSERT(observers, "this property should only store non-null values");
+ return observers;
+ }
+ observers =
+ new SVGFilterObserverListForCSSProp(effects->mFilters.AsSpan(), aFrame);
+ NS_ADDREF(observers);
+ aFrame->AddProperty(FilterProperty(), observers);
+ return observers;
+}
+
+static SVGObserverUtils::ReferenceState GetAndObserveFilters(
+ SVGFilterObserverListForCSSProp* aObserverList,
+ nsTArray<SVGFilterFrame*>* aFilterFrames) {
+ if (!aObserverList) {
+ return SVGObserverUtils::eHasNoRefs;
+ }
+
+ const nsTArray<RefPtr<SVGFilterObserver>>& observers =
+ aObserverList->GetObservers();
+ if (observers.IsEmpty()) {
+ return SVGObserverUtils::eHasNoRefs;
+ }
+
+ for (uint32_t i = 0; i < observers.Length(); i++) {
+ SVGFilterFrame* filter = observers[i]->GetAndObserveFilterFrame();
+ if (!filter) {
+ if (aFilterFrames) {
+ aFilterFrames->Clear();
+ }
+ return SVGObserverUtils::eHasRefsSomeInvalid;
+ }
+ if (aFilterFrames) {
+ aFilterFrames->AppendElement(filter);
+ }
+ }
+
+ return SVGObserverUtils::eHasRefsAllValid;
+}
+
+SVGObserverUtils::ReferenceState SVGObserverUtils::GetAndObserveFilters(
+ nsIFrame* aFilteredFrame, nsTArray<SVGFilterFrame*>* aFilterFrames) {
+ SVGFilterObserverListForCSSProp* observerList =
+ GetOrCreateFilterObserverListForCSS(aFilteredFrame);
+ return mozilla::GetAndObserveFilters(observerList, aFilterFrames);
+}
+
+SVGObserverUtils::ReferenceState SVGObserverUtils::GetFiltersIfObserving(
+ nsIFrame* aFilteredFrame, nsTArray<SVGFilterFrame*>* aFilterFrames) {
+ SVGFilterObserverListForCSSProp* observerList =
+ aFilteredFrame->GetProperty(FilterProperty());
+ return mozilla::GetAndObserveFilters(observerList, aFilterFrames);
+}
+
+already_AddRefed<nsISupports> SVGObserverUtils::ObserveFiltersForCanvasContext(
+ CanvasRenderingContext2D* aContext, Element* aCanvasElement,
+ const Span<const StyleFilter> aFilters) {
+ return do_AddRef(new SVGFilterObserverListForCanvasContext(
+ aContext, aCanvasElement, aFilters));
+}
+
+void SVGObserverUtils::DetachFromCanvasContext(nsISupports* aAutoObserver) {
+ static_cast<SVGFilterObserverListForCanvasContext*>(aAutoObserver)
+ ->DetachFromContext();
+}
+
+static SVGPaintingProperty* GetOrCreateClipPathObserver(
+ nsIFrame* aClippedFrame) {
+ MOZ_ASSERT(!aClippedFrame->GetPrevContinuation(),
+ "Require first continuation");
+
+ const nsStyleSVGReset* svgStyleReset = aClippedFrame->StyleSVGReset();
+ if (!svgStyleReset->mClipPath.IsUrl()) {
+ return nullptr;
+ }
+ const auto& url = svgStyleReset->mClipPath.AsUrl();
+ RefPtr<URLAndReferrerInfo> pathURI =
+ ResolveURLUsingLocalRef(aClippedFrame, url);
+ return GetPaintingProperty(pathURI, aClippedFrame, ClipPathProperty());
+}
+
+SVGObserverUtils::ReferenceState SVGObserverUtils::GetAndObserveClipPath(
+ nsIFrame* aClippedFrame, SVGClipPathFrame** aClipPathFrame) {
+ if (aClipPathFrame) {
+ *aClipPathFrame = nullptr;
+ }
+ SVGPaintingProperty* observers = GetOrCreateClipPathObserver(aClippedFrame);
+ if (!observers) {
+ return eHasNoRefs;
+ }
+ bool frameTypeOK = true;
+ SVGClipPathFrame* frame =
+ static_cast<SVGClipPathFrame*>(observers->GetAndObserveReferencedFrame(
+ LayoutFrameType::SVGClipPath, &frameTypeOK));
+ // Note that, unlike for filters, a reference to an ID that doesn't exist
+ // is not invalid for clip-path or mask.
+ if (!frameTypeOK || (frame && !frame->IsValid())) {
+ return eHasRefsSomeInvalid;
+ }
+ if (aClipPathFrame) {
+ *aClipPathFrame = frame;
+ }
+ return frame ? eHasRefsAllValid : eHasNoRefs;
+}
+
+static SVGMaskObserverList* GetOrCreateMaskObserverList(
+ nsIFrame* aMaskedFrame) {
+ MOZ_ASSERT(!aMaskedFrame->GetPrevContinuation(),
+ "Require first continuation");
+
+ const nsStyleSVGReset* style = aMaskedFrame->StyleSVGReset();
+ if (!style->HasMask()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(style->mMask.mImageCount > 0);
+
+ bool found;
+ SVGMaskObserverList* prop = aMaskedFrame->GetProperty(MaskProperty(), &found);
+ if (found) {
+ MOZ_ASSERT(prop, "this property should only store non-null values");
+ return prop;
+ }
+ prop = new SVGMaskObserverList(aMaskedFrame);
+ NS_ADDREF(prop);
+ aMaskedFrame->AddProperty(MaskProperty(), prop);
+ return prop;
+}
+
+SVGObserverUtils::ReferenceState SVGObserverUtils::GetAndObserveMasks(
+ nsIFrame* aMaskedFrame, nsTArray<SVGMaskFrame*>* aMaskFrames) {
+ SVGMaskObserverList* observerList = GetOrCreateMaskObserverList(aMaskedFrame);
+ if (!observerList) {
+ return eHasNoRefs;
+ }
+
+ const nsTArray<RefPtr<SVGPaintingProperty>>& observers =
+ observerList->GetObservers();
+ if (observers.IsEmpty()) {
+ return eHasNoRefs;
+ }
+
+ ReferenceState state = eHasRefsAllValid;
+
+ for (size_t i = 0; i < observers.Length(); i++) {
+ bool frameTypeOK = true;
+ SVGMaskFrame* maskFrame =
+ static_cast<SVGMaskFrame*>(observers[i]->GetAndObserveReferencedFrame(
+ LayoutFrameType::SVGMask, &frameTypeOK));
+ MOZ_ASSERT(!maskFrame || frameTypeOK);
+ // XXXjwatt: this looks fishy
+ if (!frameTypeOK) {
+ // We can not find the specific SVG mask resource in the downloaded SVG
+ // document. There are two possibilities:
+ // 1. The given resource id is invalid.
+ // 2. The given resource id refers to a viewbox.
+ //
+ // Hand it over to the style image.
+ observerList->ResolveImage(i);
+ state = eHasRefsSomeInvalid;
+ }
+ if (aMaskFrames) {
+ aMaskFrames->AppendElement(maskFrame);
+ }
+ }
+
+ return state;
+}
+
+SVGGeometryElement* SVGObserverUtils::GetAndObserveTextPathsPath(
+ nsIFrame* aTextPathFrame) {
+ // Continuations can come and go during reflow, and we don't need to observe
+ // the referenced element more than once for a given node.
+ aTextPathFrame = aTextPathFrame->FirstContinuation();
+
+ SVGTextPathObserver* property =
+ aTextPathFrame->GetProperty(HrefAsTextPathProperty());
+
+ if (!property) {
+ nsIContent* content = aTextPathFrame->GetContent();
+ nsAutoString href;
+ static_cast<SVGTextPathElement*>(content)->HrefAsString(href);
+ if (href.IsEmpty()) {
+ return nullptr; // no URL
+ }
+
+ // There's no clear refererer policy spec about non-CSS SVG resource
+ // references Bug 1415044 to investigate which referrer we should use
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ ReferrerInfo::CreateForSVGResources(content->OwnerDoc());
+ RefPtr<URLAndReferrerInfo> target =
+ ResolveURLUsingLocalRef(aTextPathFrame, href, referrerInfo);
+
+ property =
+ GetEffectProperty(target, aTextPathFrame, HrefAsTextPathProperty());
+ if (!property) {
+ return nullptr;
+ }
+ }
+
+ Element* element = property->GetAndObserveReferencedElement();
+ return (element && element->IsNodeOfType(nsINode::eSHAPE))
+ ? static_cast<SVGGeometryElement*>(element)
+ : nullptr;
+}
+
+void SVGObserverUtils::InitiateResourceDocLoads(nsIFrame* aFrame) {
+ // We create observer objects and attach them to aFrame, but we do not
+ // make aFrame start observing the referenced frames.
+ Unused << GetOrCreateFilterObserverListForCSS(aFrame);
+ Unused << GetOrCreateClipPathObserver(aFrame);
+ Unused << GetOrCreateMaskObserverList(aFrame);
+}
+
+void SVGObserverUtils::RemoveTextPathObserver(nsIFrame* aTextPathFrame) {
+ aTextPathFrame->RemoveProperty(HrefAsTextPathProperty());
+}
+
+nsIFrame* SVGObserverUtils::GetAndObserveTemplate(
+ nsIFrame* aFrame, HrefToTemplateCallback aGetHref) {
+ SVGTemplateElementObserver* observer =
+ aFrame->GetProperty(HrefToTemplateProperty());
+
+ if (!observer) {
+ nsAutoString href;
+ aGetHref(href);
+ if (href.IsEmpty()) {
+ return nullptr; // no URL
+ }
+
+ // Convert href to an nsIURI
+ nsIContent* content = aFrame->GetContent();
+
+ nsCOMPtr<nsIURI> baseURI = content->GetBaseURI();
+ if (nsContentUtils::IsLocalRefURL(href)) {
+ baseURI = GetBaseURLForLocalRef(content, baseURI);
+ }
+ nsCOMPtr<nsIURI> targetURI;
+ nsContentUtils::NewURIWithDocumentCharset(
+ getter_AddRefs(targetURI), href, content->GetUncomposedDoc(), baseURI);
+
+ // There's no clear refererer policy spec about non-CSS SVG resource
+ // references. Bug 1415044 to investigate which referrer we should use.
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ ReferrerInfo::CreateForSVGResources(content->OwnerDoc());
+ RefPtr<URLAndReferrerInfo> target =
+ new URLAndReferrerInfo(targetURI, referrerInfo);
+
+ observer = GetEffectProperty(target, aFrame, HrefToTemplateProperty());
+ }
+
+ return observer ? observer->GetAndObserveReferencedFrame() : nullptr;
+}
+
+void SVGObserverUtils::RemoveTemplateObserver(nsIFrame* aFrame) {
+ aFrame->RemoveProperty(HrefToTemplateProperty());
+}
+
+Element* SVGObserverUtils::GetAndObserveBackgroundImage(nsIFrame* aFrame,
+ const nsAtom* aHref) {
+ bool found;
+ URIObserverHashtable* hashtable =
+ aFrame->GetProperty(BackgroundImageProperty(), &found);
+ if (!found) {
+ hashtable = new URIObserverHashtable();
+ aFrame->AddProperty(BackgroundImageProperty(), hashtable);
+ } else {
+ MOZ_ASSERT(hashtable, "this property should only store non-null values");
+ }
+
+ nsAutoString elementId = u"#"_ns + nsDependentAtomString(aHref);
+ nsCOMPtr<nsIURI> targetURI;
+ nsContentUtils::NewURIWithDocumentCharset(
+ getter_AddRefs(targetURI), elementId,
+ aFrame->GetContent()->GetUncomposedDoc(),
+ aFrame->GetContent()->GetBaseURI());
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ ReferrerInfo::CreateForSVGResources(aFrame->GetContent()->OwnerDoc());
+ RefPtr<URLAndReferrerInfo> url =
+ new URLAndReferrerInfo(targetURI, referrerInfo);
+
+ return static_cast<SVGMozElementObserver*>(
+ hashtable
+ ->LookupOrInsertWith(
+ url,
+ [&] {
+ return MakeRefPtr<SVGMozElementObserver>(
+ url, aFrame,
+ /* aWatchImage */ true);
+ })
+ .get())
+ ->GetAndObserveReferencedElement();
+}
+
+Element* SVGObserverUtils::GetAndObserveBackgroundClip(nsIFrame* aFrame) {
+ bool found;
+ BackgroundClipRenderingObserver* obs =
+ aFrame->GetProperty(BackgroundClipObserverProperty(), &found);
+ if (!found) {
+ obs = new BackgroundClipRenderingObserver(aFrame);
+ NS_ADDREF(obs);
+ aFrame->AddProperty(BackgroundClipObserverProperty(), obs);
+ }
+
+ return obs->GetAndObserveReferencedElement();
+}
+
+SVGPaintServerFrame* SVGObserverUtils::GetAndObservePaintServer(
+ nsIFrame* aPaintedFrame, StyleSVGPaint nsStyleSVG::*aPaint) {
+ // If we're looking at a frame within SVG text, then we need to look up
+ // to find the right frame to get the painting property off. We should at
+ // least look up past a text frame, and if the text frame's parent is the
+ // anonymous block frame, then we look up to its parent (the SVGTextFrame).
+ nsIFrame* paintedFrame = aPaintedFrame;
+ if (paintedFrame->GetContent()->IsText()) {
+ paintedFrame = paintedFrame->GetParent();
+ nsIFrame* grandparent = paintedFrame->GetParent();
+ if (grandparent && grandparent->IsSVGTextFrame()) {
+ paintedFrame = grandparent;
+ }
+ }
+
+ const nsStyleSVG* svgStyle = paintedFrame->StyleSVG();
+ if (!(svgStyle->*aPaint).kind.IsPaintServer()) {
+ return nullptr;
+ }
+
+ RefPtr<URLAndReferrerInfo> paintServerURL = ResolveURLUsingLocalRef(
+ paintedFrame, (svgStyle->*aPaint).kind.AsPaintServer());
+
+ MOZ_ASSERT(aPaint == &nsStyleSVG::mFill || aPaint == &nsStyleSVG::mStroke);
+ PaintingPropertyDescriptor propDesc =
+ (aPaint == &nsStyleSVG::mFill) ? FillProperty() : StrokeProperty();
+ SVGPaintingProperty* property =
+ GetPaintingProperty(paintServerURL, paintedFrame, propDesc);
+ if (!property) {
+ return nullptr;
+ }
+ nsIFrame* result = property->GetAndObserveReferencedFrame();
+ if (!result) {
+ return nullptr;
+ }
+
+ LayoutFrameType type = result->Type();
+ if (type != LayoutFrameType::SVGLinearGradient &&
+ type != LayoutFrameType::SVGRadialGradient &&
+ type != LayoutFrameType::SVGPattern) {
+ return nullptr;
+ }
+
+ return static_cast<SVGPaintServerFrame*>(result);
+}
+
+void SVGObserverUtils::UpdateEffects(nsIFrame* aFrame) {
+ NS_ASSERTION(aFrame->GetContent()->IsElement(),
+ "aFrame's content should be an element");
+
+ aFrame->RemoveProperty(FilterProperty());
+ aFrame->RemoveProperty(MaskProperty());
+ aFrame->RemoveProperty(ClipPathProperty());
+ aFrame->RemoveProperty(MarkerStartProperty());
+ aFrame->RemoveProperty(MarkerMidProperty());
+ aFrame->RemoveProperty(MarkerEndProperty());
+ aFrame->RemoveProperty(FillProperty());
+ aFrame->RemoveProperty(StrokeProperty());
+ aFrame->RemoveProperty(BackgroundImageProperty());
+
+ // Ensure that the filter is repainted correctly
+ // We can't do that in OnRenderingChange as the referenced frame may
+ // not be valid
+ GetOrCreateFilterObserverListForCSS(aFrame);
+
+ if (aFrame->IsSVGGeometryFrame() &&
+ static_cast<SVGGeometryElement*>(aFrame->GetContent())->IsMarkable()) {
+ // Set marker properties here to avoid reference loops
+ RefPtr<URLAndReferrerInfo> markerURL =
+ GetMarkerURI(aFrame, &nsStyleSVG::mMarkerStart);
+ GetEffectProperty(markerURL, aFrame, MarkerStartProperty());
+ markerURL = GetMarkerURI(aFrame, &nsStyleSVG::mMarkerMid);
+ GetEffectProperty(markerURL, aFrame, MarkerMidProperty());
+ markerURL = GetMarkerURI(aFrame, &nsStyleSVG::mMarkerEnd);
+ GetEffectProperty(markerURL, aFrame, MarkerEndProperty());
+ }
+}
+
+void SVGObserverUtils::AddRenderingObserver(Element* aElement,
+ SVGRenderingObserver* aObserver) {
+ SVGRenderingObserverSet* observers = GetObserverSet(aElement);
+ if (!observers) {
+ observers = new SVGRenderingObserverSet();
+ aElement->SetProperty(nsGkAtoms::renderingobserverset, observers,
+ nsINode::DeleteProperty<SVGRenderingObserverSet>);
+ }
+ aElement->SetHasRenderingObservers(true);
+ observers->Add(aObserver);
+}
+
+void SVGObserverUtils::RemoveRenderingObserver(
+ Element* aElement, SVGRenderingObserver* aObserver) {
+ SVGRenderingObserverSet* observers = GetObserverSet(aElement);
+ if (observers) {
+ NS_ASSERTION(observers->Contains(aObserver),
+ "removing observer from an element we're not observing?");
+ observers->Remove(aObserver);
+ if (observers->IsEmpty()) {
+ aElement->SetHasRenderingObservers(false);
+ }
+ }
+}
+
+void SVGObserverUtils::RemoveAllRenderingObservers(Element* aElement) {
+ SVGRenderingObserverSet* observers = GetObserverSet(aElement);
+ if (observers) {
+ observers->RemoveAll();
+ aElement->SetHasRenderingObservers(false);
+ }
+}
+
+void SVGObserverUtils::InvalidateRenderingObservers(nsIFrame* aFrame) {
+ NS_ASSERTION(!aFrame->GetPrevContinuation(),
+ "aFrame must be first continuation");
+
+ nsIContent* content = aFrame->GetContent();
+ if (!content || !content->IsElement()) {
+ return;
+ }
+
+ // If the rendering has changed, the bounds may well have changed too:
+ aFrame->RemoveProperty(SVGUtils::ObjectBoundingBoxProperty());
+
+ SVGRenderingObserverSet* observers = GetObserverSet(content->AsElement());
+ if (observers) {
+ observers->InvalidateAll();
+ return;
+ }
+
+ // Check ancestor SVG containers. The root frame cannot be of type
+ // eSVGContainer so we don't have to check f for null here.
+ for (nsIFrame* f = aFrame->GetParent();
+ f->IsFrameOfType(nsIFrame::eSVGContainer); f = f->GetParent()) {
+ if (f->GetContent()->IsElement()) {
+ observers = GetObserverSet(f->GetContent()->AsElement());
+ if (observers) {
+ observers->InvalidateAll();
+ return;
+ }
+ }
+ }
+}
+
+void SVGObserverUtils::InvalidateDirectRenderingObservers(
+ Element* aElement, uint32_t aFlags /* = 0 */) {
+ if (nsIFrame* frame = aElement->GetPrimaryFrame()) {
+ // If the rendering has changed, the bounds may well have changed too:
+ frame->RemoveProperty(SVGUtils::ObjectBoundingBoxProperty());
+ }
+
+ if (aElement->HasRenderingObservers()) {
+ SVGRenderingObserverSet* observers = GetObserverSet(aElement);
+ if (observers) {
+ if (aFlags & INVALIDATE_REFLOW) {
+ observers->InvalidateAllForReflow();
+ } else {
+ observers->InvalidateAll();
+ }
+ }
+ }
+}
+
+void SVGObserverUtils::InvalidateDirectRenderingObservers(
+ nsIFrame* aFrame, uint32_t aFlags /* = 0 */) {
+ nsIContent* content = aFrame->GetContent();
+ if (content && content->IsElement()) {
+ InvalidateDirectRenderingObservers(content->AsElement(), aFlags);
+ }
+}
+
+already_AddRefed<nsIURI> SVGObserverUtils::GetBaseURLForLocalRef(
+ nsIContent* content, nsIURI* aDocURI) {
+ MOZ_ASSERT(content);
+
+ // Content is in a shadow tree. If this URL was specified in the subtree
+ // referenced by the <use>, element, and that subtree came from a separate
+ // resource document, then we want the fragment-only URL to resolve to an
+ // element from the resource document. Otherwise, the URL was specified
+ // somewhere in the document with the <use> element, and we want the
+ // fragment-only URL to resolve to an element in that document.
+ if (SVGUseElement* use = content->GetContainingSVGUseShadowHost()) {
+ if (nsIURI* originalURI = use->GetSourceDocURI()) {
+ bool isEqualsExceptRef = false;
+ aDocURI->EqualsExceptRef(originalURI, &isEqualsExceptRef);
+ if (isEqualsExceptRef) {
+ return do_AddRef(originalURI);
+ }
+ }
+ }
+
+ // For a local-reference URL, resolve that fragment against the current
+ // document that relative URLs are resolved against.
+ return do_AddRef(content->OwnerDoc()->GetDocumentURI());
+}
+
+already_AddRefed<URLAndReferrerInfo> SVGObserverUtils::GetFilterURI(
+ nsIFrame* aFrame, const StyleFilter& aFilter) {
+ MOZ_ASSERT(!aFrame->StyleEffects()->mFilters.IsEmpty() ||
+ !aFrame->StyleEffects()->mBackdropFilters.IsEmpty() ||
+ !aFrame->GetContent()->GetParent());
+ MOZ_ASSERT(aFilter.IsUrl());
+ return ResolveURLUsingLocalRef(aFrame, aFilter.AsUrl());
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGObserverUtils.h b/layout/svg/SVGObserverUtils.h
new file mode 100644
index 0000000000..17bd012078
--- /dev/null
+++ b/layout/svg/SVGObserverUtils.h
@@ -0,0 +1,420 @@
+/* -*- 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 LAYOUT_SVG_SVGOBSERVERUTILS_H_
+#define LAYOUT_SVG_SVGOBSERVERUTILS_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/IDTracker.h"
+#include "FrameProperties.h"
+#include "nsID.h"
+#include "nsIFrame.h" // only for LayoutFrameType
+#include "nsIMutationObserver.h"
+#include "nsISupports.h"
+#include "nsISupportsImpl.h"
+#include "nsIReferrerInfo.h"
+#include "nsStringFwd.h"
+#include "nsStubMutationObserver.h"
+#include "nsStyleStruct.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsAtom;
+class nsIFrame;
+class nsIURI;
+
+namespace mozilla {
+class SVGClipPathFrame;
+class SVGFilterFrame;
+class SVGMarkerFrame;
+class SVGMaskFrame;
+class SVGPaintServerFrame;
+
+namespace dom {
+class CanvasRenderingContext2D;
+class Element;
+class SVGGeometryElement;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+
+/*
+ * This class contains URL and referrer information (referrer and referrer
+ * policy).
+ * We use it to pass to svg system instead of nsIURI. The object brings referrer
+ * and referrer policy so we can send correct Referer headers.
+ */
+class URLAndReferrerInfo {
+ public:
+ URLAndReferrerInfo(nsIURI* aURI, nsIReferrerInfo* aReferrerInfo)
+ : mURI(aURI), mReferrerInfo(aReferrerInfo) {
+ MOZ_ASSERT(aURI);
+ }
+
+ URLAndReferrerInfo(nsIURI* aURI, const URLExtraData& aExtraData)
+ : mURI(aURI), mReferrerInfo(aExtraData.ReferrerInfo()) {
+ MOZ_ASSERT(aURI);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(URLAndReferrerInfo)
+
+ nsIURI* GetURI() const { return mURI; }
+ nsIReferrerInfo* GetReferrerInfo() const { return mReferrerInfo; }
+
+ bool operator==(const URLAndReferrerInfo& aRHS) const;
+
+ private:
+ ~URLAndReferrerInfo() = default;
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+};
+
+/**
+ * This interface allows us to be notified when a piece of SVG content is
+ * re-rendered.
+ *
+ * Concrete implementations of this base class need to implement
+ * GetReferencedElementWithoutObserving to specify the SVG element that
+ * they'd like to monitor for rendering changes, and they need to implement
+ * OnRenderingChange to specify how we'll react when that content gets
+ * re-rendered. They also need to implement a constructor and destructor,
+ * which should call StartObserving and StopObserving, respectively.
+ *
+ * The referenced element is generally looked up and stored during
+ * construction. If the referenced element is in an extenal SVG resource
+ * document, the lookup code will initiate loading of the external resource and
+ * OnRenderingChange will be called once the element in the external resource
+ * is available.
+ *
+ * Although the referenced element may be found and stored during construction,
+ * observing for rendering changes does not start until requested.
+ */
+class SVGRenderingObserver : public nsStubMutationObserver {
+ protected:
+ virtual ~SVGRenderingObserver() = default;
+
+ public:
+ using Element = dom::Element;
+
+ SVGRenderingObserver() : mInObserverSet(false) {}
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ /**
+ * Called when non-DOM-mutation changes to the observed element should likely
+ * cause the rendering of our observer to change. This includes changes to
+ * CSS computed values, but also changes to rendering observers that the
+ * observed element itself may have (for example, when we're being used to
+ * observe an SVG pattern, and an element in that pattern references and
+ * observes a gradient that has changed).
+ */
+ void OnNonDOMMutationRenderingChange();
+
+ // When a SVGRenderingObserver list gets forcibly cleared, it uses this
+ // callback to notify every observer that's cleared from it, so they can
+ // react.
+ void NotifyEvictedFromRenderingObserverSet();
+
+ nsIFrame* GetAndObserveReferencedFrame();
+ /**
+ * @param aOK this is only for the convenience of callers. We set *aOK to
+ * false if the frame is the wrong type
+ */
+ nsIFrame* GetAndObserveReferencedFrame(mozilla::LayoutFrameType aFrameType,
+ bool* aOK);
+
+ Element* GetAndObserveReferencedElement();
+
+ virtual bool ObservesReflow() { return false; }
+
+ protected:
+ void StartObserving();
+ void StopObserving();
+
+ /**
+ * Called whenever the rendering of the observed element may have changed.
+ *
+ * More specifically, this method is called whenever DOM mutation occurs in
+ * the observed element's subtree, or whenever
+ * SVGObserverUtils::InvalidateRenderingObservers or
+ * SVGObserverUtils::InvalidateDirectRenderingObservers is called for the
+ * observed element's frame.
+ *
+ * Subclasses should override this method to handle rendering changes
+ * appropriately.
+ */
+ virtual void OnRenderingChange() = 0;
+
+ virtual Element* GetReferencedElementWithoutObserving() = 0;
+
+#ifdef DEBUG
+ void DebugObserverSet();
+#endif
+
+ // Whether we're in our observed element's observer set at this time.
+ bool mInObserverSet;
+};
+
+class SVGObserverUtils {
+ public:
+ using CanvasRenderingContext2D = dom::CanvasRenderingContext2D;
+ using Element = dom::Element;
+ using SVGGeometryElement = dom::SVGGeometryElement;
+ using HrefToTemplateCallback = const std::function<void(nsAString&)>&;
+
+ /**
+ * Ensures that that if the given frame requires any resources that are in
+ * SVG resource documents that the loading of those documents is initiated.
+ * This does not make aFrame start to observe any elements that it
+ * references.
+ */
+ static void InitiateResourceDocLoads(nsIFrame* aFrame);
+
+ /**
+ * Called when changes to an element (e.g. CSS property changes) cause its
+ * frame to start/stop referencing (or reference different) SVG resource
+ * elements. (_Not_ called for changes to referenced resource elements.)
+ *
+ * This function handles such changes by discarding _all_ the frame's SVG
+ * effects frame properties (causing those properties to stop watching their
+ * target element). It also synchronously (re)creates the filter and marker
+ * frame properties (XXX why not the other properties?), which makes it
+ * useful for initializing those properties during first reflow.
+ *
+ * XXX rename to something more meaningful like RefreshResourceReferences?
+ */
+ static void UpdateEffects(nsIFrame* aFrame);
+
+ /**
+ * @param aFrame must be a first-continuation.
+ */
+ static void AddRenderingObserver(Element* aElement,
+ SVGRenderingObserver* aObserver);
+ /**
+ * @param aFrame must be a first-continuation.
+ */
+ static void RemoveRenderingObserver(Element* aElement,
+ SVGRenderingObserver* aObserver);
+
+ /**
+ * Removes all rendering observers from aElement.
+ */
+ static void RemoveAllRenderingObservers(Element* aElement);
+
+ /**
+ * This can be called on any frame. We invalidate the observers of aFrame's
+ * element, if any, or else walk up to the nearest observable SVG parent
+ * frame with observers and invalidate them instead.
+ *
+ * Note that this method is very different to e.g.
+ * MutationObservers::AttributeChanged which walks up the content node tree
+ * all the way to the root node (not stopping if it encounters a non-container
+ * SVG node) invalidating all mutation observers (not just
+ * SVGRenderingObservers) on all nodes along the way (not just the first
+ * node it finds with observers). In other words, by doing all the
+ * things in parentheses in the preceding sentence, this method uses
+ * knowledge about our implementation and what can be affected by SVG effects
+ * to make invalidation relatively lightweight when an SVG effect changes.
+ */
+ static void InvalidateRenderingObservers(nsIFrame* aFrame);
+
+ enum { INVALIDATE_REFLOW = 1 };
+
+ enum ReferenceState {
+ /// Has no references to SVG filters (may still have CSS filter functions!)
+ eHasNoRefs,
+ eHasRefsAllValid,
+ eHasRefsSomeInvalid,
+ };
+
+ /**
+ * This can be called on any element or frame. Only direct observers of this
+ * (frame's) element, if any, are invalidated.
+ */
+ static void InvalidateDirectRenderingObservers(Element* aElement,
+ uint32_t aFlags = 0);
+ static void InvalidateDirectRenderingObservers(nsIFrame* aFrame,
+ uint32_t aFlags = 0);
+
+ /**
+ * Get the paint server for aPaintedFrame.
+ */
+ static SVGPaintServerFrame* GetAndObservePaintServer(
+ nsIFrame* aPaintedFrame, mozilla::StyleSVGPaint nsStyleSVG::*aPaint);
+
+ /**
+ * Get the start/mid/end-markers for the given frame, and add the frame as
+ * an observer to those markers. Returns true if at least one marker type is
+ * found, false otherwise.
+ */
+ static bool GetAndObserveMarkers(nsIFrame* aMarkedFrame,
+ SVGMarkerFrame* (*aFrames)[3]);
+
+ /**
+ * Get the frames of the SVG filters applied to the given frame, and add the
+ * frame as an observer to those filter frames.
+ *
+ * NOTE! A return value of eHasNoRefs does NOT mean that there are no filters
+ * to be applied, only that there are no references to SVG filter elements.
+ *
+ * XXX Callers other than ComputePostEffectsInkOverflowRect and
+ * SVGUtils::GetPostFilterInkOverflowRect should not need to initiate
+ * observing. If we have a bug that causes invalidation (which would remove
+ * observers) between reflow and painting, then we don't really want to
+ * re-add abservers during painting. That has the potential to hide logic
+ * bugs, or cause later invalidation problems. However, let's not change
+ * that behavior just yet due to the regression potential.
+ */
+ static ReferenceState GetAndObserveFilters(
+ nsIFrame* aFilteredFrame, nsTArray<SVGFilterFrame*>* aFilterFrames);
+
+ /**
+ * If the given frame is already observing SVG filters, this function gets
+ * those filters. If the frame is not already observing filters this
+ * function assumes that it doesn't have anything to observe.
+ */
+ static ReferenceState GetFiltersIfObserving(
+ nsIFrame* aFilteredFrame, nsTArray<SVGFilterFrame*>* aFilterFrames);
+
+ /**
+ * Starts observing filters for a <canvas> element's CanvasRenderingContext2D.
+ *
+ * Returns a RAII object that the caller should make sure is released once
+ * the CanvasRenderingContext2D is no longer using them (that is, when the
+ * CanvasRenderingContext2D "drawing style state" on which the filters were
+ * set is destroyed or has its filter style reset).
+ *
+ * XXXjwatt: It's a bit unfortunate that both we and
+ * CanvasRenderingContext2D::UpdateFilter process the list of StyleFilter
+ * objects separately. It would be better to refactor things so that we only
+ * do that work once.
+ */
+ static already_AddRefed<nsISupports> ObserveFiltersForCanvasContext(
+ CanvasRenderingContext2D* aContext, Element* aCanvasElement,
+ Span<const StyleFilter> aFilters);
+
+ /**
+ * Called when cycle collecting CanvasRenderingContext2D, and requires the
+ * RAII object returned from ObserveFiltersForCanvasContext to be passed in.
+ *
+ * XXXjwatt: I don't think this is doing anything useful. All we do under
+ * this function is clear a raw C-style (i.e. not strong) pointer. That's
+ * clearly not helping in breaking any cycles. The fact that we MOZ_CRASH
+ * in OnRenderingChange if that pointer is null indicates that this isn't
+ * even doing anything useful in terms of preventing further invalidation
+ * from any observed filters.
+ */
+ static void DetachFromCanvasContext(nsISupports* aAutoObserver);
+
+ /**
+ * Get the frame of the SVG clipPath applied to aClippedFrame, if any, and
+ * set up aClippedFrame as a rendering observer of the clipPath's frame, to
+ * be invalidated if it changes.
+ *
+ * Currently we only have support for 'clip-path' with a single item, but the
+ * spec. now says 'clip-path' can be set to an arbitrary number of items.
+ * Once we support that, aClipPathFrame will need to be an nsTArray as it
+ * is for 'filter' and 'mask'. Currently a return value of eHasNoRefs means
+ * that there is no clipping at all, but once we support more than one item
+ * then - as for filter and mask - we could still have basic shape clipping
+ * to apply even if there are no references to SVG clipPath elements.
+ *
+ * Note that, unlike for filters, a reference to an ID that doesn't exist
+ * is not invalid for clip-path or mask. We will return eHasNoRefs in that
+ * case.
+ */
+ static ReferenceState GetAndObserveClipPath(
+ nsIFrame* aClippedFrame, SVGClipPathFrame** aClipPathFrame);
+
+ /**
+ * If masking is applied to aMaskedFrame, gets an array of any SVG masks
+ * that are referenced, setting up aMaskFrames as a rendering observer of
+ * those masks (if any).
+ *
+ * NOTE! A return value of eHasNoRefs does NOT mean that there are no masks
+ * to be applied, only that there are no references to SVG mask elements.
+ *
+ * Note that, unlike for filters, a reference to an ID that doesn't exist
+ * is not invalid for clip-path or mask. We will return eHasNoRefs in that
+ * case.
+ */
+ static ReferenceState GetAndObserveMasks(
+ nsIFrame* aMaskedFrame, nsTArray<SVGMaskFrame*>* aMaskFrames);
+
+ /**
+ * Get the SVGGeometryElement that is referenced by aTextPathFrame, and make
+ * aTextPathFrame start observing rendering changes to that element.
+ */
+ static SVGGeometryElement* GetAndObserveTextPathsPath(
+ nsIFrame* aTextPathFrame);
+
+ /**
+ * Make aTextPathFrame stop observing rendering changes to the
+ * SVGGeometryElement that it references, if any.
+ */
+ static void RemoveTextPathObserver(nsIFrame* aTextPathFrame);
+
+ /**
+ * Gets the nsIFrame of a referenced SVG "template" element, if any, and
+ * makes aFrame start observing rendering changes to the template element.
+ *
+ * Template elements: some elements like gradients, pattern or filter can
+ * reference another element of the same type using their 'href' attribute,
+ * and use that element as a template that provides attributes or content
+ * that is missing from the referring element.
+ *
+ * The frames that this function is called for do not have a common base
+ * class, which is why it is necessary to pass in a function that can be
+ * used as a callback to lazily get the href value, if necessary.
+ */
+ static nsIFrame* GetAndObserveTemplate(nsIFrame* aFrame,
+ HrefToTemplateCallback aGetHref);
+
+ static void RemoveTemplateObserver(nsIFrame* aFrame);
+
+ /**
+ * Gets an arbitrary element and starts observing it. Used to implement
+ * '-moz-element'.
+ *
+ * Note that bug 1496065 has been filed to remove support for referencing
+ * arbitrary elements using '-moz-element'.
+ */
+ static Element* GetAndObserveBackgroundImage(nsIFrame* aFrame,
+ const nsAtom* aHref);
+
+ /**
+ * Gets an arbitrary element and starts observing it. Used to detect
+ * invalidation changes for background-clip:text.
+ */
+ static Element* GetAndObserveBackgroundClip(nsIFrame* aFrame);
+
+ /**
+ * A helper function to resolve filter URL.
+ */
+ static already_AddRefed<URLAndReferrerInfo> GetFilterURI(
+ nsIFrame* aFrame, const StyleFilter& aFilter);
+
+ /**
+ * Return a baseURL for resolving a local-ref URL.
+ *
+ * @param aContent an element which uses a local-ref property. Here are some
+ * examples:
+ * <rect fill=url(#foo)>
+ * <circle clip-path=url(#foo)>
+ * <use xlink:href="#foo">
+ */
+ static already_AddRefed<nsIURI> GetBaseURLForLocalRef(nsIContent* aContent,
+ nsIURI* aDocURI);
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGOBSERVERUTILS_H_
diff --git a/layout/svg/SVGOuterSVGFrame.cpp b/layout/svg/SVGOuterSVGFrame.cpp
new file mode 100644
index 0000000000..c49e5769e6
--- /dev/null
+++ b/layout/svg/SVGOuterSVGFrame.cpp
@@ -0,0 +1,1033 @@
+/* -*- 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:
+#include "SVGOuterSVGFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfxContext.h"
+#include "nsDisplayList.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsObjectLoadingContent.h"
+#include "nsSubDocumentFrame.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGForeignObjectFrame.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/SVGSVGElement.h"
+#include "mozilla/dom/SVGViewElement.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+namespace mozilla {
+
+//----------------------------------------------------------------------
+// Implementation helpers
+
+void SVGOuterSVGFrame::RegisterForeignObject(SVGForeignObjectFrame* aFrame) {
+ NS_ASSERTION(aFrame, "Who on earth is calling us?!");
+
+ if (!mForeignObjectHash) {
+ mForeignObjectHash = MakeUnique<nsTHashSet<SVGForeignObjectFrame*>>();
+ }
+
+ NS_ASSERTION(!mForeignObjectHash->Contains(aFrame),
+ "SVGForeignObjectFrame already registered!");
+
+ mForeignObjectHash->Insert(aFrame);
+
+ NS_ASSERTION(mForeignObjectHash->Contains(aFrame),
+ "Failed to register SVGForeignObjectFrame!");
+}
+
+void SVGOuterSVGFrame::UnregisterForeignObject(SVGForeignObjectFrame* aFrame) {
+ NS_ASSERTION(aFrame, "Who on earth is calling us?!");
+ NS_ASSERTION(mForeignObjectHash && mForeignObjectHash->Contains(aFrame),
+ "SVGForeignObjectFrame not in registry!");
+ return mForeignObjectHash->Remove(aFrame);
+}
+
+} // namespace mozilla
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsContainerFrame* NS_NewSVGOuterSVGFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGOuterSVGFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGOuterSVGFrame)
+
+SVGOuterSVGFrame::SVGOuterSVGFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : SVGDisplayContainerFrame(aStyle, aPresContext, kClassID),
+ mCallingReflowSVG(false),
+ mFullZoom(PresContext()->GetFullZoom()),
+ mViewportInitialized(false),
+ mIsRootContent(false),
+ mIsInObjectOrEmbed(false),
+ mIsInIframe(false) {
+ // Outer-<svg> has CSS layout, so remove this bit:
+ RemoveStateBits(NS_FRAME_SVG_LAYOUT);
+ AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
+}
+
+// The CSS Containment spec says that size-contained replaced elements must be
+// treated as having an intrinsic width and height of 0. That's applicable to
+// outer SVG frames, unless they're the outermost element (in which case
+// they're not really "replaced", and there's no outer context to contain sizes
+// from leaking into). Hence, we check for a parent element before we bother
+// testing for 'contain:size'.
+static inline ContainSizeAxes ContainSizeAxesIfApplicable(
+ const SVGOuterSVGFrame* aFrame) {
+ if (!aFrame->GetContent()->GetParent()) {
+ return ContainSizeAxes(false, false);
+ }
+ return aFrame->GetContainSizeAxes();
+}
+
+void SVGOuterSVGFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svg),
+ "Content is not an SVG 'svg' element!");
+
+ AddStateBits(NS_FRAME_REFLOW_ROOT | NS_FRAME_FONT_INFLATION_CONTAINER |
+ NS_FRAME_FONT_INFLATION_FLOW_ROOT);
+
+ // Check for conditional processing attributes here rather than in
+ // nsCSSFrameConstructor::FindSVGData because we want to avoid
+ // simply giving failing outer <svg> elements an SVGContainerFrame.
+ // We don't create other SVG frames if PassesConditionalProcessingTests
+ // returns false, but since we do create SVGOuterSVGFrame frames we
+ // prevent them from painting by [ab]use NS_FRAME_IS_NONDISPLAY. The
+ // frame will be recreated via an nsChangeHint_ReconstructFrame restyle if
+ // the value returned by PassesConditionalProcessingTests changes.
+ auto* svg = static_cast<SVGSVGElement*>(aContent);
+ if (!svg->PassesConditionalProcessingTests()) {
+ AddStateBits(NS_FRAME_IS_NONDISPLAY);
+ }
+
+ SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow);
+
+ Document* doc = mContent->GetUncomposedDoc();
+ mIsRootContent = doc && doc->GetRootElement() == mContent;
+
+ if (mIsRootContent) {
+ if (nsCOMPtr<nsIDocShell> docShell = PresContext()->GetDocShell()) {
+ RefPtr<BrowsingContext> bc = docShell->GetBrowsingContext();
+ if (const Maybe<nsString>& type = bc->GetEmbedderElementType()) {
+ mIsInObjectOrEmbed =
+ nsGkAtoms::object->Equals(*type) || nsGkAtoms::embed->Equals(*type);
+ mIsInIframe = nsGkAtoms::iframe->Equals(*type);
+ }
+ }
+ }
+
+ MaybeSendIntrinsicSizeAndRatioToEmbedder();
+}
+
+//----------------------------------------------------------------------
+// nsQueryFrame methods
+
+NS_QUERYFRAME_HEAD(SVGOuterSVGFrame)
+ NS_QUERYFRAME_ENTRY(SVGOuterSVGFrame)
+ NS_QUERYFRAME_ENTRY(ISVGSVGFrame)
+NS_QUERYFRAME_TAIL_INHERITING(SVGDisplayContainerFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods
+//----------------------------------------------------------------------
+// reflowing
+
+/* virtual */
+nscoord SVGOuterSVGFrame::GetMinISize(gfxContext* aRenderingContext) {
+ nscoord result;
+ DISPLAY_MIN_INLINE_SIZE(this, result);
+
+ // If this ever changes to return something other than zero, then
+ // nsSubDocumentFrame::GetMinISize will also need to change.
+ result = nscoord(0);
+
+ return result;
+}
+
+/* virtual */
+nscoord SVGOuterSVGFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ nscoord result;
+ DISPLAY_PREF_INLINE_SIZE(this, result);
+
+ SVGSVGElement* svg = static_cast<SVGSVGElement*>(GetContent());
+ WritingMode wm = GetWritingMode();
+ const SVGAnimatedLength& isize =
+ wm.IsVertical() ? svg->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]
+ : svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH];
+
+ if (Maybe<nscoord> containISize =
+ ContainSizeAxesIfApplicable(this).ContainIntrinsicISize(*this)) {
+ result = *containISize;
+ } else if (isize.IsPercentage()) {
+ // If we are here, our inline size attribute is a percentage either
+ // explicitly (via an attribute value) or implicitly (by being unset, which
+ // is treated as 100%). The following if-condition, deciding to return
+ // either the fallback intrinsic size or zero, is made to match blink and
+ // webkit's behavior for webcompat.
+ if (isize.IsExplicitlySet() || StylePosition()->ISize(wm).HasPercent() ||
+ !GetAspectRatio()) {
+ result = wm.IsVertical() ? kFallbackIntrinsicSize.height
+ : kFallbackIntrinsicSize.width;
+ } else {
+ result = nscoord(0);
+ }
+ } else {
+ result = nsPresContext::CSSPixelsToAppUnits(isize.GetAnimValue(svg));
+ if (result < 0) {
+ result = nscoord(0);
+ }
+ }
+
+ return result;
+}
+
+/* virtual */
+IntrinsicSize SVGOuterSVGFrame::GetIntrinsicSize() {
+ // XXXjwatt Note that here we want to return the CSS width/height if they're
+ // specified and we're embedded inside an nsIObjectLoadingContent.
+
+ const auto containAxes = ContainSizeAxesIfApplicable(this);
+ if (containAxes.IsBoth()) {
+ // Intrinsic size of 'contain:size' replaced elements is determined by
+ // contain-intrinsic-size, defaulting to 0x0.
+ return containAxes.ContainIntrinsicSize(IntrinsicSize(0, 0), *this);
+ }
+
+ SVGSVGElement* content = static_cast<SVGSVGElement*>(GetContent());
+ const SVGAnimatedLength& width =
+ content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH];
+ const SVGAnimatedLength& height =
+ content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT];
+
+ IntrinsicSize intrinsicSize;
+
+ if (!width.IsPercentage()) {
+ nscoord val =
+ nsPresContext::CSSPixelsToAppUnits(width.GetAnimValue(content));
+ intrinsicSize.width.emplace(std::max(val, 0));
+ }
+
+ if (!height.IsPercentage()) {
+ nscoord val =
+ nsPresContext::CSSPixelsToAppUnits(height.GetAnimValue(content));
+ intrinsicSize.height.emplace(std::max(val, 0));
+ }
+
+ return containAxes.ContainIntrinsicSize(intrinsicSize, *this);
+}
+
+/* virtual */
+AspectRatio SVGOuterSVGFrame::GetIntrinsicRatio() const {
+ if (ContainSizeAxesIfApplicable(this).IsAny()) {
+ return AspectRatio();
+ }
+
+ // We only have an intrinsic size/ratio if our width and height attributes
+ // are both specified and set to non-percentage values, or we have a viewBox
+ // rect: https://svgwg.org/svg2-draft/coords.html#SizingSVGInCSS
+
+ auto* content = static_cast<SVGSVGElement*>(GetContent());
+ const SVGAnimatedLength& width =
+ content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH];
+ const SVGAnimatedLength& height =
+ content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT];
+ if (!width.IsPercentage() && !height.IsPercentage()) {
+ // Use width/height ratio only if
+ // 1. it's not a degenerate ratio, and
+ // 2. width and height are non-negative numbers.
+ // Otherwise, we use the viewbox rect.
+ // https://github.com/w3c/csswg-drafts/issues/6286
+ const float w = width.GetAnimValue(content);
+ const float h = height.GetAnimValue(content);
+ if (w > 0.0f && h > 0.0f) {
+ return AspectRatio::FromSize(w, h);
+ }
+ }
+
+ const auto& viewBox = content->GetViewBoxInternal();
+ if (viewBox.HasRect()) {
+ const auto& anim = viewBox.GetAnimValue();
+ return AspectRatio::FromSize(anim.width, anim.height);
+ }
+
+ return SVGDisplayContainerFrame::GetIntrinsicRatio();
+}
+
+/* virtual */
+nsIFrame::SizeComputationResult SVGOuterSVGFrame::ComputeSize(
+ gfxContext* aRenderingContext, WritingMode aWritingMode,
+ const LogicalSize& aCBSize, nscoord aAvailableISize,
+ const LogicalSize& aMargin, const LogicalSize& aBorderPadding,
+ const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) {
+ if (IsRootOfImage() || mIsInObjectOrEmbed) {
+ // The embedding element has sized itself using the CSS replaced element
+ // sizing rules, using our intrinsic dimensions as necessary. The SVG spec
+ // says that the width and height of embedded SVG is overridden by the
+ // width and height of the embedding element, so we just need to size to
+ // the viewport that the embedding element has established for us.
+ return {aCBSize, AspectRatioUsage::None};
+ }
+
+ LogicalSize cbSize = aCBSize;
+ IntrinsicSize intrinsicSize = GetIntrinsicSize();
+
+ if (!mContent->GetParent()) {
+ // We're the root of the outermost browsing context, so we need to scale
+ // cbSize by the full-zoom so that SVGs with percentage width/height zoom:
+
+ NS_ASSERTION(aCBSize.ISize(aWritingMode) != NS_UNCONSTRAINEDSIZE &&
+ aCBSize.BSize(aWritingMode) != NS_UNCONSTRAINEDSIZE,
+ "root should not have auto-width/height containing block");
+
+ if (!mIsInIframe) {
+ cbSize.ISize(aWritingMode) *= PresContext()->GetFullZoom();
+ cbSize.BSize(aWritingMode) *= PresContext()->GetFullZoom();
+ }
+
+ // We also need to honour the width and height attributes' default values
+ // of 100% when we're the root of a browsing context. (GetIntrinsicSize()
+ // doesn't report these since there's no such thing as a percentage
+ // intrinsic size. Also note that explicit percentage values are mapped
+ // into style, so the following isn't for them.)
+
+ SVGSVGElement* content = static_cast<SVGSVGElement*>(GetContent());
+
+ const SVGAnimatedLength& width =
+ content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH];
+ if (width.IsPercentage()) {
+ MOZ_ASSERT(!intrinsicSize.width,
+ "GetIntrinsicSize should have reported no intrinsic width");
+ float val = width.GetAnimValInSpecifiedUnits() / 100.0f;
+ intrinsicSize.width.emplace(std::max(val, 0.0f) *
+ cbSize.Width(aWritingMode));
+ }
+
+ const SVGAnimatedLength& height =
+ content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT];
+ NS_ASSERTION(aCBSize.BSize(aWritingMode) != NS_UNCONSTRAINEDSIZE,
+ "root should not have auto-height containing block");
+ if (height.IsPercentage()) {
+ MOZ_ASSERT(!intrinsicSize.height,
+ "GetIntrinsicSize should have reported no intrinsic height");
+ float val = height.GetAnimValInSpecifiedUnits() / 100.0f;
+ intrinsicSize.height.emplace(std::max(val, 0.0f) *
+ cbSize.Height(aWritingMode));
+ }
+ MOZ_ASSERT(intrinsicSize.height && intrinsicSize.width,
+ "We should have just handled the only situation where"
+ "we lack an intrinsic height or width.");
+ }
+
+ return {ComputeSizeWithIntrinsicDimensions(
+ aRenderingContext, aWritingMode, intrinsicSize, GetAspectRatio(),
+ cbSize, aMargin, aBorderPadding, aSizeOverrides, aFlags),
+ AspectRatioUsage::None};
+}
+
+void SVGOuterSVGFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("SVGOuterSVGFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+ NS_FRAME_TRACE(
+ NS_FRAME_TRACE_CALLS,
+ ("enter SVGOuterSVGFrame::Reflow: availSize=%d,%d",
+ aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
+
+ MOZ_ASSERT(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow");
+
+ aDesiredSize.Width() =
+ aReflowInput.ComputedWidth() +
+ aReflowInput.ComputedPhysicalBorderPadding().LeftRight();
+ aDesiredSize.Height() =
+ aReflowInput.ComputedHeight() +
+ aReflowInput.ComputedPhysicalBorderPadding().TopBottom();
+
+ NS_ASSERTION(!GetPrevInFlow(), "SVG can't currently be broken across pages.");
+
+ SVGSVGElement* svgElem = static_cast<SVGSVGElement*>(GetContent());
+
+ auto* anonKid = static_cast<SVGOuterSVGAnonChildFrame*>(
+ PrincipalChildList().FirstChild());
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ // Initialize
+ svgElem->UpdateHasChildrenOnlyTransform();
+ }
+
+ // If our SVG viewport has changed, update our content and notify.
+ // http://www.w3.org/TR/SVG11/coords.html#ViewportSpace
+
+ svgFloatSize newViewportSize(
+ nsPresContext::AppUnitsToFloatCSSPixels(aReflowInput.ComputedWidth()),
+ nsPresContext::AppUnitsToFloatCSSPixels(aReflowInput.ComputedHeight()));
+
+ svgFloatSize oldViewportSize = svgElem->GetViewportSize();
+
+ uint32_t changeBits = 0;
+ if (newViewportSize != oldViewportSize) {
+ // When our viewport size changes, we may need to update the overflow rects
+ // of our child frames. This is the case if:
+ //
+ // * We have a real/synthetic viewBox (a children-only transform), since
+ // the viewBox transform will change as the viewport dimensions change.
+ //
+ // * We do not have a real/synthetic viewBox, but the last time we
+ // reflowed (or the last time UpdateOverflow() was called) we did.
+ //
+ // We only handle the former case here, in which case we mark all our child
+ // frames as dirty so that we reflow them below and update their overflow
+ // rects.
+ //
+ // In the latter case, updating of overflow rects is handled for removal of
+ // real viewBox (the viewBox attribute) in AttributeChanged. Synthetic
+ // viewBox "removal" (e.g. a document references the same SVG via both an
+ // <svg:image> and then as a CSS background image (a synthetic viewBox is
+ // used when painting the former, but not when painting the latter)) is
+ // handled in SVGSVGElement::FlushImageTransformInvalidation.
+ //
+ if (svgElem->HasViewBoxOrSyntheticViewBox()) {
+ nsIFrame* anonChild = PrincipalChildList().FirstChild();
+ anonChild->MarkSubtreeDirty();
+ for (nsIFrame* child : anonChild->PrincipalChildList()) {
+ child->MarkSubtreeDirty();
+ }
+ }
+ changeBits |= COORD_CONTEXT_CHANGED;
+ svgElem->SetViewportSize(newViewportSize);
+ }
+ if (mFullZoom != PresContext()->GetFullZoom() && !mIsInIframe) {
+ changeBits |= FULL_ZOOM_CHANGED;
+ mFullZoom = PresContext()->GetFullZoom();
+ }
+ if (changeBits) {
+ NotifyViewportOrTransformChanged(changeBits);
+ }
+ mViewportInitialized = true;
+
+ // Now that we've marked the necessary children as dirty, call
+ // ReflowSVG() or ReflowSVGNonDisplayText() on them, depending
+ // on whether we are non-display.
+ mCallingReflowSVG = true;
+ if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ ReflowSVGNonDisplayText(this);
+ } else {
+ // Update the mRects and ink overflow rects of all our descendants,
+ // including our anonymous wrapper kid:
+ anonKid->ReflowSVG();
+ MOZ_ASSERT(!anonKid->GetNextSibling(),
+ "We should have one anonymous child frame wrapping our real "
+ "children");
+ }
+ mCallingReflowSVG = false;
+
+ // Set our anonymous kid's offset from our border box:
+ anonKid->SetPosition(GetContentRectRelativeToSelf().TopLeft());
+
+ // Including our size in our overflow rects regardless of the value of
+ // 'background', 'border', etc. makes sure that we usually (when we clip to
+ // our content area) don't have to keep changing our overflow rects as our
+ // descendants move about (see perf comment below). Including our size in our
+ // scrollable overflow rect also makes sure that we scroll if we're too big
+ // for our viewport.
+ //
+ // <svg> never allows scrolling to anything outside its mRect (only panning),
+ // so we must always keep our scrollable overflow set to our size.
+ //
+ // With regards to ink overflow, we always clip root-<svg> (see our
+ // BuildDisplayList method) regardless of the value of the 'overflow'
+ // property since that is per-spec, even for the initial 'visible' value. For
+ // that reason there's no point in adding descendant ink overflow to our
+ // own when this frame is for a root-<svg>. That said, there's also a very
+ // good performance reason for us wanting to avoid doing so. If we did, then
+ // the frame's overflow would often change as descendants that are partially
+ // or fully outside its rect moved (think animation on/off screen), and that
+ // would cause us to do a full NS_FRAME_IS_DIRTY reflow and repaint of the
+ // entire document tree each such move (see bug 875175).
+ //
+ // So it's only non-root outer-<svg> that has the ink overflow of its
+ // descendants added to its own. (Note that the default user-agent style
+ // sheet makes 'hidden' the default value for :not(root(svg)), so usually
+ // FinishAndStoreOverflow will still clip this back to the frame's rect.)
+ //
+ // WARNING!! Keep UpdateBounds below in sync with whatever we do for our
+ // overflow rects here! (Again, see bug 875175.)
+ //
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+
+ // An outer SVG will be here as a nondisplay if it fails the conditional
+ // processing test. In that case, we don't maintain its overflow.
+ if (!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ if (!mIsRootContent) {
+ aDesiredSize.mOverflowAreas.InkOverflow().UnionRect(
+ aDesiredSize.mOverflowAreas.InkOverflow(),
+ anonKid->InkOverflowRect() + anonKid->GetPosition());
+ }
+ FinishAndStoreOverflow(&aDesiredSize);
+ }
+
+ NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
+ ("exit SVGOuterSVGFrame::Reflow: size=%d,%d",
+ aDesiredSize.Width(), aDesiredSize.Height()));
+}
+
+void SVGOuterSVGFrame::DidReflow(nsPresContext* aPresContext,
+ const ReflowInput* aReflowInput) {
+ SVGDisplayContainerFrame::DidReflow(aPresContext, aReflowInput);
+
+ // Make sure elements styled by :hover get updated if script/animation moves
+ // them under or out from under the pointer:
+ PresShell()->SynthesizeMouseMove(false);
+}
+
+/* virtual */
+void SVGOuterSVGFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) {
+ // See the comments in Reflow above.
+
+ // WARNING!! Keep this in sync with Reflow above!
+
+ if (!mIsRootContent) {
+ nsIFrame* anonKid = PrincipalChildList().FirstChild();
+ aOverflowAreas.InkOverflow().UnionRect(
+ aOverflowAreas.InkOverflow(),
+ anonKid->InkOverflowRect() + anonKid->GetPosition());
+ }
+}
+
+//----------------------------------------------------------------------
+// container methods
+
+/**
+ * Used to paint/hit-test SVG when SVG display lists are disabled.
+ */
+class nsDisplayOuterSVG final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayOuterSVG(nsDisplayListBuilder* aBuilder, SVGOuterSVGFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayOuterSVG);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayOuterSVG)
+
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) override;
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aContext) override;
+
+ virtual void ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override;
+
+ NS_DISPLAY_DECL_NAME("SVGOuterSVG", TYPE_SVG_OUTER_SVG)
+};
+
+void nsDisplayOuterSVG::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect, HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) {
+ SVGOuterSVGFrame* outerSVGFrame = static_cast<SVGOuterSVGFrame*>(mFrame);
+
+ nsPoint refFrameToContentBox =
+ ToReferenceFrame() +
+ outerSVGFrame->GetContentRectRelativeToSelf().TopLeft();
+
+ nsPoint pointRelativeToContentBox =
+ nsPoint(aRect.x + aRect.width / 2, aRect.y + aRect.height / 2) -
+ refFrameToContentBox;
+
+ gfxPoint svgViewportRelativePoint =
+ gfxPoint(pointRelativeToContentBox.x, pointRelativeToContentBox.y) /
+ AppUnitsPerCSSPixel();
+
+ auto* anonKid = static_cast<SVGOuterSVGAnonChildFrame*>(
+ outerSVGFrame->PrincipalChildList().FirstChild());
+
+ nsIFrame* frame =
+ SVGUtils::HitTestChildren(anonKid, svgViewportRelativePoint);
+ if (frame) {
+ aOutFrames->AppendElement(frame);
+ }
+}
+
+void nsDisplayOuterSVG::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aContext) {
+#if defined(DEBUG) && defined(SVG_DEBUG_PAINT_TIMING)
+ PRTime start = PR_Now();
+#endif
+
+ // Create an SVGAutoRenderState so we can call SetPaintingToWindow on it.
+ SVGAutoRenderState state(aContext->GetDrawTarget());
+
+ if (aBuilder->IsPaintingToWindow()) {
+ state.SetPaintingToWindow(true);
+ }
+
+ nsRect viewportRect =
+ mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame();
+
+ nsRect clipRect = GetPaintRect(aBuilder, aContext).Intersect(viewportRect);
+
+ uint32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+
+ nsIntRect contentAreaDirtyRect =
+ (clipRect - viewportRect.TopLeft()).ToOutsidePixels(appUnitsPerDevPixel);
+
+ gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
+ viewportRect.TopLeft(), appUnitsPerDevPixel);
+
+ aContext->Save();
+ imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags());
+ // We include the offset of our frame and a scale from device pixels to user
+ // units (i.e. CSS px) in the matrix that we pass to our children):
+ gfxMatrix tm = SVGUtils::GetCSSPxToDevPxMatrix(mFrame) *
+ gfxMatrix::Translation(devPixelOffset);
+ SVGUtils::PaintFrameWithEffects(mFrame, *aContext, tm, imgParams,
+ &contentAreaDirtyRect);
+ aContext->Restore();
+
+#if defined(DEBUG) && defined(SVG_DEBUG_PAINT_TIMING)
+ PRTime end = PR_Now();
+ printf("SVG Paint Timing: %f ms\n", (end - start) / 1000.0);
+#endif
+}
+
+nsRegion SVGOuterSVGFrame::FindInvalidatedForeignObjectFrameChildren(
+ nsIFrame* aFrame) {
+ nsRegion result;
+ if (mForeignObjectHash && mForeignObjectHash->Count()) {
+ for (const auto& key : *mForeignObjectHash) {
+ result.Or(result, key->GetInvalidRegion());
+ }
+ }
+ return result;
+}
+
+void nsDisplayOuterSVG::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const {
+ auto* frame = static_cast<SVGOuterSVGFrame*>(mFrame);
+ frame->InvalidateSVG(frame->FindInvalidatedForeignObjectFrameChildren(frame));
+
+ nsRegion result = frame->GetInvalidRegion();
+ result.MoveBy(ToReferenceFrame());
+ frame->ClearInvalidRegion();
+
+ nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
+ aInvalidRegion->Or(*aInvalidRegion, result);
+}
+
+nsresult SVGOuterSVGFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ if (aNameSpaceID == kNameSpaceID_None &&
+ !HasAnyStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_NONDISPLAY)) {
+ if (aAttribute == nsGkAtoms::viewBox ||
+ aAttribute == nsGkAtoms::preserveAspectRatio ||
+ aAttribute == nsGkAtoms::transform) {
+ // make sure our cached transform matrix gets (lazily) updated
+ mCanvasTM = nullptr;
+
+ SVGUtils::NotifyChildrenOfSVGChange(
+ PrincipalChildList().FirstChild(),
+ aAttribute == nsGkAtoms::viewBox
+ ? TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED
+ : TRANSFORM_CHANGED);
+
+ if (aAttribute != nsGkAtoms::transform) {
+ static_cast<SVGSVGElement*>(GetContent())
+ ->ChildrenOnlyTransformChanged();
+ }
+ }
+ if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height ||
+ aAttribute == nsGkAtoms::viewBox) {
+ // Don't call ChildrenOnlyTransformChanged() here, since we call it
+ // under Reflow if the width/height/viewBox actually changed.
+
+ MaybeSendIntrinsicSizeAndRatioToEmbedder();
+
+ if (!mIsInObjectOrEmbed) {
+ // We are not embedded by reference, so our 'width' and 'height'
+ // attributes are not overridden (and viewBox may influence our
+ // intrinsic aspect ratio). We need to reflow.
+ PresShell()->FrameNeedsReflow(
+ this, IntrinsicDirty::FrameAncestorsAndDescendants,
+ NS_FRAME_IS_DIRTY);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+bool SVGOuterSVGFrame::IsSVGTransformed(Matrix* aOwnTransform,
+ Matrix* aFromParentTransform) const {
+ // Our anonymous child's HasChildrenOnlyTransform() implementation makes sure
+ // our children-only transforms are applied to our children. We only care
+ // about transforms that transform our own frame here.
+
+ bool foundTransform = false;
+
+ SVGSVGElement* content = static_cast<SVGSVGElement*>(GetContent());
+ SVGAnimatedTransformList* transformList = content->GetAnimatedTransformList();
+ if ((transformList && transformList->HasTransform()) ||
+ content->GetAnimateMotionTransform()) {
+ if (aOwnTransform) {
+ *aOwnTransform = gfx::ToMatrix(
+ content->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent));
+ }
+ foundTransform = true;
+ }
+
+ return foundTransform;
+}
+
+//----------------------------------------------------------------------
+// painting
+
+void SVGOuterSVGFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ return;
+ }
+
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+
+ nsRect visibleRect = aBuilder->GetVisibleRect();
+ nsRect dirtyRect = aBuilder->GetDirtyRect();
+
+ // Per-spec, we always clip root-<svg> even when 'overflow' has its initial
+ // value of 'visible'. See also the "ink overflow" comments in Reflow.
+ DisplayListClipState::AutoSaveRestore autoSR(aBuilder);
+ if (mIsRootContent || StyleDisplay()->IsScrollableOverflow()) {
+ autoSR.ClipContainingBlockDescendantsToContentBox(aBuilder, this);
+ visibleRect = visibleRect.Intersect(GetContentRectRelativeToSelf());
+ dirtyRect = dirtyRect.Intersect(GetContentRectRelativeToSelf());
+ }
+
+ nsDisplayListBuilder::AutoBuildingDisplayList building(
+ aBuilder, this, visibleRect, dirtyRect);
+
+ if ((aBuilder->IsForEventDelivery() &&
+ NS_SVGDisplayListHitTestingEnabled()) ||
+ (!aBuilder->IsForEventDelivery() && NS_SVGDisplayListPaintingEnabled())) {
+ nsDisplayList* contentList = aLists.Content();
+ nsDisplayListSet set(contentList, contentList, contentList, contentList,
+ contentList, contentList);
+ BuildDisplayListForNonBlockChildren(aBuilder, set);
+ } else if (IsVisibleForPainting() || !aBuilder->IsForPainting()) {
+ aBuilder->BuildCompositorHitTestInfoIfNeeded(this,
+ aLists.BorderBackground());
+ aLists.Content()->AppendNewToTop<nsDisplayOuterSVG>(aBuilder, this);
+ }
+}
+
+//----------------------------------------------------------------------
+// ISVGSVGFrame methods:
+
+void SVGOuterSVGFrame::NotifyViewportOrTransformChanged(uint32_t aFlags) {
+ MOZ_ASSERT(aFlags && !(aFlags & ~(COORD_CONTEXT_CHANGED | TRANSFORM_CHANGED |
+ FULL_ZOOM_CHANGED)),
+ "Unexpected aFlags value");
+
+ // No point in doing anything when were not init'ed yet:
+ if (!mViewportInitialized) {
+ return;
+ }
+
+ SVGSVGElement* content = static_cast<SVGSVGElement*>(GetContent());
+
+ if (aFlags & COORD_CONTEXT_CHANGED) {
+ if (content->HasViewBox()) {
+ // Percentage lengths on children resolve against the viewBox rect so we
+ // don't need to notify them of the viewport change, but the viewBox
+ // transform will have changed, so we need to notify them of that instead.
+ aFlags = TRANSFORM_CHANGED;
+ } else if (content->ShouldSynthesizeViewBox()) {
+ // In the case of a synthesized viewBox, the synthetic viewBox's rect
+ // changes as the viewport changes. As a result we need to maintain the
+ // COORD_CONTEXT_CHANGED flag.
+ aFlags |= TRANSFORM_CHANGED;
+ } else if (mCanvasTM && mCanvasTM->IsSingular()) {
+ // A width/height of zero will result in us having a singular mCanvasTM
+ // even when we don't have a viewBox. So we also want to recompute our
+ // mCanvasTM for this width/height change even though we don't have a
+ // viewBox.
+ aFlags |= TRANSFORM_CHANGED;
+ }
+ }
+
+ bool haveNonFulLZoomTransformChange = (aFlags & TRANSFORM_CHANGED);
+
+ if (aFlags & FULL_ZOOM_CHANGED) {
+ // Convert FULL_ZOOM_CHANGED to TRANSFORM_CHANGED:
+ aFlags = (aFlags & ~FULL_ZOOM_CHANGED) | TRANSFORM_CHANGED;
+ }
+
+ if (aFlags & TRANSFORM_CHANGED) {
+ // Make sure our canvas transform matrix gets (lazily) recalculated:
+ mCanvasTM = nullptr;
+
+ if (haveNonFulLZoomTransformChange && !(mState & NS_FRAME_IS_NONDISPLAY)) {
+ uint32_t flags =
+ (mState & NS_FRAME_IN_REFLOW) ? SVGSVGElement::eDuringReflow : 0;
+ content->ChildrenOnlyTransformChanged(flags);
+ }
+ }
+
+ SVGUtils::NotifyChildrenOfSVGChange(PrincipalChildList().FirstChild(),
+ aFlags);
+}
+
+//----------------------------------------------------------------------
+// ISVGDisplayableFrame methods:
+
+void SVGOuterSVGFrame::PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect) {
+ NS_ASSERTION(
+ PrincipalChildList().FirstChild()->IsSVGOuterSVGAnonChildFrame() &&
+ !PrincipalChildList().FirstChild()->GetNextSibling(),
+ "We should have a single, anonymous, child");
+ auto* anonKid = static_cast<SVGOuterSVGAnonChildFrame*>(
+ PrincipalChildList().FirstChild());
+ anonKid->PaintSVG(aContext, aTransform, aImgParams, aDirtyRect);
+}
+
+SVGBBox SVGOuterSVGFrame::GetBBoxContribution(
+ const gfx::Matrix& aToBBoxUserspace, uint32_t aFlags) {
+ NS_ASSERTION(
+ PrincipalChildList().FirstChild()->IsSVGOuterSVGAnonChildFrame() &&
+ !PrincipalChildList().FirstChild()->GetNextSibling(),
+ "We should have a single, anonymous, child");
+ // We must defer to our child so that we don't include our
+ // content->PrependLocalTransformsTo() transforms.
+ auto* anonKid = static_cast<SVGOuterSVGAnonChildFrame*>(
+ PrincipalChildList().FirstChild());
+ return anonKid->GetBBoxContribution(aToBBoxUserspace, aFlags);
+}
+
+//----------------------------------------------------------------------
+// SVGContainerFrame methods:
+
+gfxMatrix SVGOuterSVGFrame::GetCanvasTM() {
+ if (!mCanvasTM) {
+ SVGSVGElement* content = static_cast<SVGSVGElement*>(GetContent());
+
+ float devPxPerCSSPx = 1.0f / nsPresContext::AppUnitsToFloatCSSPixels(
+ PresContext()->AppUnitsPerDevPixel());
+
+ gfxMatrix tm = content->PrependLocalTransformsTo(
+ gfxMatrix::Scaling(devPxPerCSSPx, devPxPerCSSPx));
+ mCanvasTM = MakeUnique<gfxMatrix>(tm);
+ }
+ return *mCanvasTM;
+}
+
+//----------------------------------------------------------------------
+// Implementation helpers
+
+bool SVGOuterSVGFrame::IsRootOfImage() {
+ if (!mContent->GetParent()) {
+ // Our content is the document element
+ Document* doc = mContent->GetUncomposedDoc();
+ if (doc && doc->IsBeingUsedAsImage()) {
+ // Our document is being used as an image
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool SVGOuterSVGFrame::VerticalScrollbarNotNeeded() const {
+ const SVGAnimatedLength& height =
+ static_cast<SVGSVGElement*>(GetContent())
+ ->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT];
+ return height.IsPercentage() && height.GetBaseValInSpecifiedUnits() <= 100;
+}
+
+void SVGOuterSVGFrame::AppendDirectlyOwnedAnonBoxes(
+ nsTArray<OwnedAnonBox>& aResult) {
+ nsIFrame* anonKid = PrincipalChildList().FirstChild();
+ MOZ_ASSERT(anonKid->IsSVGOuterSVGAnonChildFrame());
+ aResult.AppendElement(OwnedAnonBox(anonKid));
+}
+
+void SVGOuterSVGFrame::MaybeSendIntrinsicSizeAndRatioToEmbedder() {
+ MaybeSendIntrinsicSizeAndRatioToEmbedder(Some(GetIntrinsicSize()),
+ Some(GetAspectRatio()));
+}
+
+void SVGOuterSVGFrame::MaybeSendIntrinsicSizeAndRatioToEmbedder(
+ Maybe<IntrinsicSize> aIntrinsicSize, Maybe<AspectRatio> aIntrinsicRatio) {
+ if (!mIsInObjectOrEmbed) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = PresContext()->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+
+ BrowsingContext* bc = docShell->GetBrowsingContext();
+ MOZ_ASSERT(bc->IsContentSubframe());
+
+ if (bc->GetParent()->IsInProcess()) {
+ if (Element* embedder = bc->GetEmbedderElement()) {
+ if (nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(embedder)) {
+ static_cast<nsObjectLoadingContent*>(olc.get())
+ ->SubdocumentIntrinsicSizeOrRatioChanged(aIntrinsicSize,
+ aIntrinsicRatio);
+ }
+ return;
+ }
+ }
+
+ if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) {
+ Unused << browserChild->SendIntrinsicSizeOrRatioChanged(aIntrinsicSize,
+ aIntrinsicRatio);
+ }
+}
+
+void SVGOuterSVGFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
+ SVGDisplayContainerFrame::DidSetComputedStyle(aOldComputedStyle);
+
+ if (!aOldComputedStyle) {
+ return;
+ }
+
+ if (aOldComputedStyle->StylePosition()->mAspectRatio !=
+ StylePosition()->mAspectRatio) {
+ // Our aspect-ratio property value changed, and an embedding <object> or
+ // <embed> might care about that.
+ MaybeSendIntrinsicSizeAndRatioToEmbedder();
+ }
+}
+
+void SVGOuterSVGFrame::DestroyFrom(nsIFrame* aDestructRoot,
+ PostDestroyData& aPostDestroyData) {
+ // This handles both the case when the root <svg> element is made display:none
+ // (and thus loses its intrinsic size and aspect ratio), and when the frame
+ // is navigated elsewhere & we need to reset parent <object>/<embed>'s
+ // recorded intrinsic size/ratio values.
+ MaybeSendIntrinsicSizeAndRatioToEmbedder(Nothing(), Nothing());
+
+ SVGDisplayContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
+}
+
+} // namespace mozilla
+
+//----------------------------------------------------------------------
+// Implementation of SVGOuterSVGAnonChildFrame
+
+nsContainerFrame* NS_NewSVGOuterSVGAnonChildFrame(
+ mozilla::PresShell* aPresShell, mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGOuterSVGAnonChildFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGOuterSVGAnonChildFrame)
+
+#ifdef DEBUG
+void SVGOuterSVGAnonChildFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ MOZ_ASSERT(aParent->IsSVGOuterSVGFrame(), "Unexpected parent");
+ SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif
+
+void SVGOuterSVGAnonChildFrame::BuildDisplayList(
+ nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) {
+ // Wrap our contents into an nsDisplaySVGWrapper.
+ // We wrap this frame instead of the SVGOuterSVGFrame so that the wrapper
+ // doesn't contain the <svg> element's CSS styles, like backgrounds or
+ // borders. Creating the nsDisplaySVGWrapper here also means that it'll be
+ // inside the nsDisplayTransform for our viewbox transform. The
+ // nsDisplaySVGWrapper's reference frame is this frame, because this frame
+ // always returns true from IsSVGTransformed.
+ nsDisplayList newList(aBuilder);
+ nsDisplayListSet set(&newList, &newList, &newList, &newList, &newList,
+ &newList);
+ BuildDisplayListForNonBlockChildren(aBuilder, set);
+ aLists.Content()->AppendNewToTop<nsDisplaySVGWrapper>(aBuilder, this,
+ &newList);
+}
+
+static Matrix ComputeOuterSVGAnonChildFrameTransform(
+ const SVGOuterSVGAnonChildFrame* aFrame) {
+ // Our elements 'transform' attribute is applied to our SVGOuterSVGFrame
+ // parent, and the element's children-only transforms are applied to us, the
+ // anonymous child frame. Since we are the child frame, we apply the
+ // children-only transforms as if they are our own transform.
+ SVGSVGElement* content = static_cast<SVGSVGElement*>(aFrame->GetContent());
+
+ if (!content->HasChildrenOnlyTransform()) {
+ return Matrix();
+ }
+
+ // Outer-<svg> doesn't use x/y, so we can pass eChildToUserSpace here.
+ gfxMatrix ownMatrix =
+ content->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace);
+
+ if (ownMatrix.HasNonTranslation()) {
+ // viewBox, currentScale and currentTranslate should only produce a
+ // rectilinear transform.
+ MOZ_ASSERT(ownMatrix.IsRectilinear(),
+ "Non-rectilinear transform will break the following logic");
+
+ // The nsDisplayTransform code will apply this transform to our frame,
+ // including to our frame position. We don't want our frame position to
+ // be scaled though, so we need to correct for that in the transform.
+ // XXX Yeah, this is a bit hacky.
+ CSSPoint pos = CSSPixel::FromAppUnits(aFrame->GetPosition());
+ CSSPoint scaledPos = CSSPoint(ownMatrix._11 * pos.x, ownMatrix._22 * pos.y);
+ CSSPoint deltaPos = scaledPos - pos;
+ ownMatrix *= gfxMatrix::Translation(-deltaPos.x, -deltaPos.y);
+ }
+
+ return gfx::ToMatrix(ownMatrix);
+}
+
+// We want this frame to be a reference frame. An easy way to achieve that is
+// to always return true from this method, even for identity transforms.
+// This frame being a reference frame ensures that the offset between this
+// <svg> element and the parent reference frame is completely absorbed by the
+// nsDisplayTransform that's created for this frame, and that this offset does
+// not affect our descendants' transforms. Consequently, if the <svg> element
+// moves, e.g. during scrolling, the transform matrices of our contents are
+// unaffected. This simplifies invalidation.
+bool SVGOuterSVGAnonChildFrame::IsSVGTransformed(
+ Matrix* aOwnTransform, Matrix* aFromParentTransform) const {
+ if (aOwnTransform) {
+ *aOwnTransform = ComputeOuterSVGAnonChildFrameTransform(this);
+ }
+
+ return true;
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGOuterSVGFrame.h b/layout/svg/SVGOuterSVGFrame.h
new file mode 100644
index 0000000000..03a839e4e5
--- /dev/null
+++ b/layout/svg/SVGOuterSVGFrame.h
@@ -0,0 +1,277 @@
+/* -*- 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 LAYOUT_SVG_SVGOUTERSVGFRAME_H_
+#define LAYOUT_SVG_SVGOUTERSVGFRAME_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ISVGSVGFrame.h"
+#include "mozilla/SVGContainerFrame.h"
+#include "mozilla/UniquePtr.h"
+#include "nsRegion.h"
+#include "nsTHashSet.h"
+
+class gfxContext;
+
+namespace mozilla {
+class AutoSVGViewHandler;
+class SVGForeignObjectFrame;
+class SVGFragmentIdentifier;
+class PresShell;
+} // namespace mozilla
+
+nsContainerFrame* NS_NewSVGOuterSVGFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+nsContainerFrame* NS_NewSVGOuterSVGAnonChildFrame(
+ mozilla::PresShell* aPresShell, mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+////////////////////////////////////////////////////////////////////////
+// SVGOuterSVGFrame class
+
+class SVGOuterSVGFrame final : public SVGDisplayContainerFrame,
+ public ISVGSVGFrame {
+ using imgDrawingParams = image::imgDrawingParams;
+
+ friend nsContainerFrame* ::NS_NewSVGOuterSVGFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+ friend class AutoSVGViewHandler;
+ friend class SVGFragmentIdentifier;
+
+ protected:
+ explicit SVGOuterSVGFrame(ComputedStyle* aStyle, nsPresContext* aPresContext);
+
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(SVGOuterSVGFrame)
+
+#ifdef DEBUG
+ ~SVGOuterSVGFrame() {
+ NS_ASSERTION(!mForeignObjectHash || mForeignObjectHash->Count() == 0,
+ "foreignObject(s) still registered!");
+ }
+#endif
+
+ // nsIFrame:
+ nscoord GetMinISize(gfxContext* aRenderingContext) override;
+ nscoord GetPrefISize(gfxContext* aRenderingContext) override;
+
+ IntrinsicSize GetIntrinsicSize() override;
+ AspectRatio GetIntrinsicRatio() const override;
+
+ SizeComputationResult ComputeSize(
+ gfxContext* aRenderingContext, WritingMode aWritingMode,
+ const LogicalSize& aCBSize, nscoord aAvailableISize,
+ const LogicalSize& aMargin, const LogicalSize& aBorderPadding,
+ const mozilla::StyleSizeOverrides& aSizeOverrides,
+ ComputeSizeFlags aFlags) override;
+
+ void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ void DidReflow(nsPresContext* aPresContext,
+ const ReflowInput* aReflowInput) override;
+
+ void UnionChildOverflow(mozilla::OverflowAreas& aOverflowAreas) override;
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ bool IsFrameOfType(uint32_t aFlags) const override {
+ return SVGDisplayContainerFrame::IsFrameOfType(
+ aFlags &
+ ~(eSupportsContainLayoutAndPaint | eReplaced | eReplacedSizing));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGOuterSVG"_ns, aResult);
+ }
+#endif
+
+ void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override;
+
+ void DestroyFrom(nsIFrame* aDestructRoot,
+ PostDestroyData& aPostDestroyData) override;
+
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ nsContainerFrame* GetContentInsertionFrame() override {
+ // Any children must be added to our single anonymous inner frame kid.
+ MOZ_ASSERT(
+ PrincipalChildList().FirstChild() &&
+ PrincipalChildList().FirstChild()->IsSVGOuterSVGAnonChildFrame(),
+ "Where is our anonymous child?");
+ return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
+ }
+
+ bool IsSVGTransformed(Matrix* aOwnTransform,
+ Matrix* aFromParentTransform) const override;
+
+ // Return our anonymous box child.
+ void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override;
+
+ // ISVGSVGFrame interface:
+ void NotifyViewportOrTransformChanged(uint32_t aFlags) override;
+
+ // ISVGDisplayableFrame methods:
+ void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect = nullptr) override;
+ SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace,
+ uint32_t aFlags) override;
+
+ // SVGContainerFrame methods:
+ gfxMatrix GetCanvasTM() override;
+
+ /* Methods to allow descendant SVGForeignObjectFrame frames to register and
+ * unregister themselves with their nearest SVGOuterSVGFrame ancestor. This
+ * is temporary until display list based invalidation is impleented for SVG.
+ * Maintaining a list of our foreignObject descendants allows us to search
+ * them for areas that need to be invalidated, without having to also search
+ * the SVG frame tree for foreignObjects. This is important so that bug 539356
+ * does not slow down SVG in general (only foreignObjects, until bug 614732 is
+ * fixed).
+ */
+ void RegisterForeignObject(SVGForeignObjectFrame* aFrame);
+ void UnregisterForeignObject(SVGForeignObjectFrame* aFrame);
+
+ bool HasChildrenOnlyTransform(Matrix* aTransform) const override {
+ // Our anonymous wrapper child must claim our children-only transforms as
+ // its own so that our real children (the frames it wraps) are transformed
+ // by them, and we must pretend we don't have any children-only transforms
+ // so that our anonymous child is _not_ transformed by them.
+ return false;
+ }
+
+ /**
+ * Return true only if the height is unspecified (defaulting to 100%) or else
+ * the height is explicitly set to a percentage value no greater than 100%.
+ */
+ bool VerticalScrollbarNotNeeded() const;
+
+ bool IsCallingReflowSVG() const { return mCallingReflowSVG; }
+
+ void InvalidateSVG(const nsRegion& aRegion) {
+ if (!aRegion.IsEmpty()) {
+ mInvalidRegion.Or(mInvalidRegion, aRegion);
+ InvalidateFrame();
+ }
+ }
+
+ void ClearInvalidRegion() { mInvalidRegion.SetEmpty(); }
+
+ const nsRegion& GetInvalidRegion() {
+ nsRect rect;
+ if (!IsInvalid(rect)) {
+ mInvalidRegion.SetEmpty();
+ }
+ return mInvalidRegion;
+ }
+
+ nsRegion FindInvalidatedForeignObjectFrameChildren(nsIFrame* aFrame);
+
+ protected:
+ bool mCallingReflowSVG;
+
+ /* Returns true if our content is the document element and our document is
+ * being used as an image.
+ */
+ bool IsRootOfImage();
+
+ void MaybeSendIntrinsicSizeAndRatioToEmbedder();
+ void MaybeSendIntrinsicSizeAndRatioToEmbedder(Maybe<IntrinsicSize>,
+ Maybe<AspectRatio>);
+
+ // This is temporary until display list based invalidation is implemented for
+ // SVG.
+ // A hash-set containing our SVGForeignObjectFrame descendants. Note we use
+ // a hash-set to avoid the O(N^2) behavior we'd get tearing down an SVG frame
+ // subtree if we were to use a list (see bug 381285 comment 20).
+ UniquePtr<nsTHashSet<SVGForeignObjectFrame*>> mForeignObjectHash;
+
+ nsRegion mInvalidRegion;
+
+ float mFullZoom;
+
+ bool mViewportInitialized;
+ bool mIsRootContent;
+ bool mIsInObjectOrEmbed;
+ bool mIsInIframe;
+};
+
+////////////////////////////////////////////////////////////////////////
+// SVGOuterSVGAnonChildFrame class
+
+/**
+ * SVGOuterSVGFrames have a single direct child that is an instance of this
+ * class, and which is used to wrap their real child frames. Such anonymous
+ * wrapper frames created from this class exist because SVG frames need their
+ * GetPosition() offset to be their offset relative to "user space" (in app
+ * units) so that they can play nicely with nsDisplayTransform. This is fine
+ * for all SVG frames except for direct children of an SVGOuterSVGFrame,
+ * since an SVGOuterSVGFrame can have CSS border and padding (unlike other
+ * SVG frames). The direct children can't include the offsets due to any such
+ * border/padding in their mRects since that would break nsDisplayTransform,
+ * but not including these offsets would break other parts of the Mozilla code
+ * that assume a frame's mRect contains its border-box-to-parent-border-box
+ * offset, in particular nsIFrame::GetOffsetTo and the functions that depend on
+ * it. Wrapping an SVGOuterSVGFrame's children in an instance of this class
+ * with its GetPosition() set to its SVGOuterSVGFrame's border/padding offset
+ * keeps both nsDisplayTransform and nsIFrame::GetOffsetTo happy.
+ *
+ * The reason that this class inherit from SVGDisplayContainerFrame rather
+ * than simply from nsContainerFrame is so that we can avoid having special
+ * handling for these inner wrappers in multiple parts of the SVG code. For
+ * example, the implementations of IsSVGTransformed and GetCanvasTM assume
+ * SVGContainerFrame instances all the way up to the SVGOuterSVGFrame.
+ */
+class SVGOuterSVGAnonChildFrame final : public SVGDisplayContainerFrame {
+ friend nsContainerFrame* ::NS_NewSVGOuterSVGAnonChildFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle);
+
+ explicit SVGOuterSVGAnonChildFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : SVGDisplayContainerFrame(aStyle, aPresContext, kClassID) {}
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGOuterSVGAnonChildFrame)
+
+#ifdef DEBUG
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGOuterSVGAnonChild"_ns, aResult);
+ }
+#endif
+
+ bool IsSVGTransformed(Matrix* aOwnTransform,
+ Matrix* aFromParentTransform) const override;
+
+ // SVGContainerFrame methods:
+ gfxMatrix GetCanvasTM() override {
+ // GetCanvasTM returns the transform from an SVG frame to the frame's
+ // SVGOuterSVGFrame's content box, so we do not include any x/y offset
+ // set on us for any CSS border or padding on our SVGOuterSVGFrame.
+ return static_cast<SVGOuterSVGFrame*>(GetParent())->GetCanvasTM();
+ }
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGOUTERSVGFRAME_H_
diff --git a/layout/svg/SVGPaintServerFrame.cpp b/layout/svg/SVGPaintServerFrame.cpp
new file mode 100644
index 0000000000..6a2b337871
--- /dev/null
+++ b/layout/svg/SVGPaintServerFrame.cpp
@@ -0,0 +1,16 @@
+/* -*- 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:
+#include "SVGPaintServerFrame.h"
+
+namespace mozilla {
+
+NS_QUERYFRAME_HEAD(SVGPaintServerFrame)
+ NS_QUERYFRAME_ENTRY(SVGPaintServerFrame)
+NS_QUERYFRAME_TAIL_INHERITING(SVGContainerFrame)
+
+} // namespace mozilla
diff --git a/layout/svg/SVGPaintServerFrame.h b/layout/svg/SVGPaintServerFrame.h
new file mode 100644
index 0000000000..cebed16441
--- /dev/null
+++ b/layout/svg/SVGPaintServerFrame.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 LAYOUT_SVG_SVGPAINTSERVERFRAME_H_
+#define LAYOUT_SVG_SVGPAINTSERVERFRAME_H_
+
+#include "gfxRect.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/SVGContainerFrame.h"
+#include "nsCOMPtr.h"
+#include "nsIFrame.h"
+#include "nsIFrame.h"
+#include "nsQueryFrame.h"
+
+class gfxContext;
+class gfxPattern;
+
+namespace mozilla {
+namespace gfx {
+class DrawTarget;
+} // namespace gfx
+
+/**
+ * RAII class used to temporarily set and remove the
+ * NS_FRAME_DRAWING_AS_PAINTSERVER frame state bit while a frame is being
+ * drawn as a paint server.
+ */
+class MOZ_RAII AutoSetRestorePaintServerState {
+ public:
+ explicit AutoSetRestorePaintServerState(nsIFrame* aFrame) : mFrame(aFrame) {
+ mFrame->AddStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER);
+ }
+ ~AutoSetRestorePaintServerState() {
+ mFrame->RemoveStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER);
+ }
+
+ private:
+ nsIFrame* mFrame;
+};
+
+class SVGPaintServerFrame : public SVGContainerFrame {
+ protected:
+ using DrawTarget = gfx::DrawTarget;
+
+ SVGPaintServerFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ ClassID aID)
+ : SVGContainerFrame(aStyle, aPresContext, aID) {
+ AddStateBits(NS_FRAME_IS_NONDISPLAY);
+ }
+
+ public:
+ using imgDrawingParams = image::imgDrawingParams;
+
+ NS_DECL_ABSTRACT_FRAME(SVGPaintServerFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_QUERYFRAME_TARGET(SVGPaintServerFrame)
+
+ /**
+ * Constructs a gfxPattern of the paint server rendering.
+ *
+ * @param aContextMatrix The transform matrix that is currently applied to
+ * the gfxContext that is being drawn to. This is needed by SVG patterns so
+ * that surfaces of the correct size can be created. (SVG gradients are
+ * vector based, so it's not used there.)
+ */
+ virtual already_AddRefed<gfxPattern> GetPaintServerPattern(
+ nsIFrame* aSource, const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix, StyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ float aOpacity, imgDrawingParams& aImgParams,
+ const gfxRect* aOverrideBounds = nullptr) = 0;
+
+ // nsIFrame methods:
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override {}
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGPAINTSERVERFRAME_H_
diff --git a/layout/svg/SVGPatternFrame.cpp b/layout/svg/SVGPatternFrame.cpp
new file mode 100644
index 0000000000..9ba9139e36
--- /dev/null
+++ b/layout/svg/SVGPatternFrame.cpp
@@ -0,0 +1,714 @@
+/* -*- 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:
+#include "SVGPatternFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "AutoReferenceChainGuard.h"
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxMatrix.h"
+#include "gfxPattern.h"
+#include "gfxPlatform.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/ISVGDisplayableFrame.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGContentUtils.h"
+#include "mozilla/SVGGeometryFrame.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/SVGPatternElement.h"
+#include "mozilla/dom/SVGUnitTypesBinding.h"
+#include "mozilla/gfx/2D.h"
+#include "nsGkAtoms.h"
+#include "nsIFrameInlines.h"
+#include "SVGAnimatedTransformList.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::dom::SVGUnitTypes_Binding;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+namespace mozilla {
+
+//----------------------------------------------------------------------
+// Implementation
+
+SVGPatternFrame::SVGPatternFrame(ComputedStyle* aStyle,
+ nsPresContext* aPresContext)
+ : SVGPaintServerFrame(aStyle, aPresContext, kClassID),
+ mSource(nullptr),
+ mLoopFlag(false),
+ mNoHRefURI(false) {}
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGPatternFrame)
+
+NS_QUERYFRAME_HEAD(SVGPatternFrame)
+ NS_QUERYFRAME_ENTRY(SVGPatternFrame)
+NS_QUERYFRAME_TAIL_INHERITING(SVGPaintServerFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods:
+
+nsresult SVGPatternFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::patternUnits ||
+ aAttribute == nsGkAtoms::patternContentUnits ||
+ aAttribute == nsGkAtoms::patternTransform ||
+ aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y ||
+ aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height ||
+ aAttribute == nsGkAtoms::preserveAspectRatio ||
+ aAttribute == nsGkAtoms::viewBox)) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(this);
+ }
+
+ if ((aNameSpaceID == kNameSpaceID_XLink ||
+ aNameSpaceID == kNameSpaceID_None) &&
+ aAttribute == nsGkAtoms::href) {
+ // Blow away our reference, if any
+ SVGObserverUtils::RemoveTemplateObserver(this);
+ mNoHRefURI = false;
+ // And update whoever references us
+ SVGObserverUtils::InvalidateDirectRenderingObservers(this);
+ }
+
+ return SVGPaintServerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+ aModType);
+}
+
+#ifdef DEBUG
+void SVGPatternFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::pattern),
+ "Content is not an SVG pattern");
+
+ SVGPaintServerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+//----------------------------------------------------------------------
+// SVGContainerFrame methods:
+
+// If our GetCanvasTM is getting called, we
+// need to return *our current* transformation
+// matrix, which depends on our units parameters
+// and X, Y, Width, and Height
+gfxMatrix SVGPatternFrame::GetCanvasTM() {
+ if (mCTM) {
+ return *mCTM;
+ }
+
+ // Do we know our rendering parent?
+ if (mSource) {
+ // Yes, use it!
+ return mSource->GetCanvasTM();
+ }
+
+ // We get here when geometry in the <pattern> container is updated
+ return gfxMatrix();
+}
+
+// -------------------------------------------------------------------------
+// Helper functions
+// -------------------------------------------------------------------------
+
+/** Calculate the maximum expansion of a matrix */
+static float MaxExpansion(const Matrix& aMatrix) {
+ // maximum expansion derivation from
+ // http://lists.cairographics.org/archives/cairo/2004-October/001980.html
+ // and also implemented in cairo_matrix_transformed_circle_major_axis
+ double a = aMatrix._11;
+ double b = aMatrix._12;
+ double c = aMatrix._21;
+ double d = aMatrix._22;
+ double f = (a * a + b * b + c * c + d * d) / 2;
+ double g = (a * a + b * b - c * c - d * d) / 2;
+ double h = a * c + b * d;
+ return sqrt(f + sqrt(g * g + h * h));
+}
+
+// The SVG specification says that the 'patternContentUnits' attribute "has no
+// effect if attribute ‘viewBox’ is specified". We still need to include a bbox
+// scale if the viewBox is specified and _patternUnits_ is set to or defaults to
+// objectBoundingBox though, since in that case the viewBox is relative to the
+// bbox
+static bool IncludeBBoxScale(const SVGAnimatedViewBox& aViewBox,
+ uint32_t aPatternContentUnits,
+ uint32_t aPatternUnits) {
+ return (!aViewBox.IsExplicitlySet() &&
+ aPatternContentUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) ||
+ (aViewBox.IsExplicitlySet() &&
+ aPatternUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX);
+}
+
+// Given the matrix for the pattern element's own transform, this returns a
+// combined matrix including the transforms applicable to its target.
+static Matrix GetPatternMatrix(uint16_t aPatternUnits,
+ const Matrix& patternTransform,
+ const gfxRect& bbox, const gfxRect& callerBBox,
+ const Matrix& callerCTM) {
+ // We really want the pattern matrix to handle translations
+ gfxFloat minx = bbox.X();
+ gfxFloat miny = bbox.Y();
+
+ if (aPatternUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ minx += callerBBox.X();
+ miny += callerBBox.Y();
+ }
+
+ float scale = 1.0f / MaxExpansion(callerCTM);
+ Matrix patternMatrix = patternTransform;
+ patternMatrix.PreScale(scale, scale);
+ patternMatrix.PreTranslate(minx, miny);
+
+ return patternMatrix;
+}
+
+static nsresult GetTargetGeometry(gfxRect* aBBox,
+ const SVGAnimatedViewBox& aViewBox,
+ uint16_t aPatternContentUnits,
+ uint16_t aPatternUnits, nsIFrame* aTarget,
+ const Matrix& aContextMatrix,
+ const gfxRect* aOverrideBounds) {
+ *aBBox =
+ aOverrideBounds
+ ? *aOverrideBounds
+ : SVGUtils::GetBBox(aTarget, SVGUtils::eUseFrameBoundsForOuterSVG |
+ SVGUtils::eBBoxIncludeFillGeometry);
+
+ // Sanity check
+ if (IncludeBBoxScale(aViewBox, aPatternContentUnits, aPatternUnits) &&
+ (aBBox->Width() <= 0 || aBBox->Height() <= 0)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // OK, now fix up the bounding box to reflect user coordinates
+ // We handle device unit scaling in pattern matrix
+ float scale = MaxExpansion(aContextMatrix);
+ if (scale <= 0) {
+ return NS_ERROR_FAILURE;
+ }
+ aBBox->Scale(scale);
+ return NS_OK;
+}
+
+already_AddRefed<SourceSurface> SVGPatternFrame::PaintPattern(
+ const DrawTarget* aDrawTarget, Matrix* patternMatrix,
+ const Matrix& aContextMatrix, nsIFrame* aSource,
+ StyleSVGPaint nsStyleSVG::*aFillOrStroke, float aGraphicOpacity,
+ const gfxRect* aOverrideBounds, imgDrawingParams& aImgParams) {
+ /*
+ * General approach:
+ * Set the content geometry stuff
+ * Calculate our bbox (using x,y,width,height & patternUnits &
+ * patternTransform)
+ * Create the surface
+ * Calculate the content transformation matrix
+ * Get our children (we may need to get them from another Pattern)
+ * Call SVGPaint on all of our children
+ * Return
+ */
+
+ SVGPatternFrame* patternWithChildren = GetPatternWithChildren();
+ if (!patternWithChildren) {
+ // Either no kids or a bad reference
+ return nullptr;
+ }
+ nsIFrame* firstKid = patternWithChildren->mFrames.FirstChild();
+
+ const SVGAnimatedViewBox& viewBox = GetViewBox();
+
+ uint16_t patternContentUnits =
+ GetEnumValue(SVGPatternElement::PATTERNCONTENTUNITS);
+ uint16_t patternUnits = GetEnumValue(SVGPatternElement::PATTERNUNITS);
+
+ /*
+ * Get the content geometry information. This is a little tricky --
+ * our parent is probably a <defs>, but we are rendering in the context
+ * of some geometry source. Our content geometry information needs to
+ * come from our rendering parent as opposed to our content parent. We
+ * get that information from aSource, which is passed to us from the
+ * backend renderer.
+ *
+ * There are three "geometries" that we need:
+ * 1) The bounding box for the pattern. We use this to get the
+ * width and height for the surface, and as the return to
+ * GetBBox.
+ * 2) The transformation matrix for the pattern. This is not *quite*
+ * the same as the canvas transformation matrix that we will
+ * provide to our rendering children since we "fudge" it a little
+ * to get the renderer to handle the translations correctly for us.
+ * 3) The CTM that we return to our children who make up the pattern.
+ */
+
+ // Get all of the information we need from our "caller" -- i.e.
+ // the geometry that is being rendered with a pattern
+ gfxRect callerBBox;
+ if (NS_FAILED(GetTargetGeometry(&callerBBox, viewBox, patternContentUnits,
+ patternUnits, aSource, aContextMatrix,
+ aOverrideBounds))) {
+ return nullptr;
+ }
+
+ // Construct the CTM that we will provide to our children when we
+ // render them into the tile.
+ gfxMatrix ctm = ConstructCTM(viewBox, patternContentUnits, patternUnits,
+ callerBBox, aContextMatrix, aSource);
+ if (ctm.IsSingular()) {
+ return nullptr;
+ }
+
+ if (patternWithChildren->mCTM) {
+ *patternWithChildren->mCTM = ctm;
+ } else {
+ patternWithChildren->mCTM = MakeUnique<gfxMatrix>(ctm);
+ }
+
+ // Get the bounding box of the pattern. This will be used to determine
+ // the size of the surface, and will also be used to define the bounding
+ // box for the pattern tile.
+ gfxRect bbox =
+ GetPatternRect(patternUnits, callerBBox, aContextMatrix, aSource);
+ if (bbox.Width() <= 0.0 || bbox.Height() <= 0.0) {
+ return nullptr;
+ }
+
+ // Get the pattern transform
+ Matrix patternTransform = ToMatrix(GetPatternTransform());
+
+ // revert the vector effect transform so that the pattern appears unchanged
+ if (aFillOrStroke == &nsStyleSVG::mStroke) {
+ gfxMatrix userToOuterSVG;
+ if (SVGUtils::GetNonScalingStrokeTransform(aSource, &userToOuterSVG)) {
+ patternTransform *= ToMatrix(userToOuterSVG);
+ if (patternTransform.IsSingular()) {
+ NS_WARNING("Singular matrix painting non-scaling-stroke");
+ return nullptr;
+ }
+ }
+ }
+
+ // Get the transformation matrix that we will hand to the renderer's pattern
+ // routine.
+ *patternMatrix = GetPatternMatrix(patternUnits, patternTransform, bbox,
+ callerBBox, aContextMatrix);
+ if (patternMatrix->IsSingular()) {
+ return nullptr;
+ }
+
+ // Now that we have all of the necessary geometries, we can
+ // create our surface.
+ gfxRect transformedBBox =
+ ThebesRect(patternTransform.TransformBounds(ToRect(bbox)));
+
+ bool resultOverflows;
+ IntSize surfaceSize =
+ SVGUtils::ConvertToSurfaceSize(transformedBBox.Size(), &resultOverflows);
+
+ // 0 disables rendering, < 0 is an error
+ if (surfaceSize.width <= 0 || surfaceSize.height <= 0) {
+ return nullptr;
+ }
+
+ gfxFloat patternWidth = bbox.Width();
+ gfxFloat patternHeight = bbox.Height();
+
+ if (resultOverflows || patternWidth != surfaceSize.width ||
+ patternHeight != surfaceSize.height) {
+ // scale drawing to pattern surface size
+ gfxMatrix tempTM = gfxMatrix(surfaceSize.width / patternWidth, 0.0, 0.0,
+ surfaceSize.height / patternHeight, 0.0, 0.0);
+ patternWithChildren->mCTM->PreMultiply(tempTM);
+
+ // and rescale pattern to compensate
+ patternMatrix->PreScale(patternWidth / surfaceSize.width,
+ patternHeight / surfaceSize.height);
+ }
+
+ RefPtr<DrawTarget> dt = aDrawTarget->CreateSimilarDrawTargetWithBacking(
+ surfaceSize, SurfaceFormat::B8G8R8A8);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+ dt->ClearRect(Rect(0, 0, surfaceSize.width, surfaceSize.height));
+
+ RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt);
+ MOZ_ASSERT(ctx); // already checked the draw target above
+
+ if (aGraphicOpacity != 1.0f) {
+ ctx->Save();
+ ctx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, aGraphicOpacity);
+ }
+
+ // OK, now render -- note that we use "firstKid", which
+ // we got at the beginning because it takes care of the
+ // referenced pattern situation for us
+
+ if (aSource->IsSVGGeometryFrameOrSubclass()) {
+ // Set the geometrical parent of the pattern we are rendering
+ patternWithChildren->mSource = static_cast<SVGGeometryFrame*>(aSource);
+ }
+
+ // Delay checking NS_FRAME_DRAWING_AS_PAINTSERVER bit until here so we can
+ // give back a clear surface if there's a loop
+ if (!patternWithChildren->HasAnyStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER)) {
+ AutoSetRestorePaintServerState paintServer(patternWithChildren);
+ for (nsIFrame* kid = firstKid; kid; kid = kid->GetNextSibling()) {
+ gfxMatrix tm = *(patternWithChildren->mCTM);
+
+ // The CTM of each frame referencing us can be different
+ ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
+ if (SVGFrame) {
+ SVGFrame->NotifySVGChanged(ISVGDisplayableFrame::TRANSFORM_CHANGED);
+ tm = SVGUtils::GetTransformMatrixInUserSpace(kid) * tm;
+ }
+
+ SVGUtils::PaintFrameWithEffects(kid, *ctx, tm, aImgParams);
+ }
+ }
+
+ patternWithChildren->mSource = nullptr;
+
+ if (aGraphicOpacity != 1.0f) {
+ ctx->PopGroupAndBlend();
+ ctx->Restore();
+ }
+
+ // caller now owns the surface
+ return dt->GetBackingSurface();
+}
+
+/* Will probably need something like this... */
+// How do we handle the insertion of a new frame?
+// We really don't want to rerender this every time,
+// do we?
+SVGPatternFrame* SVGPatternFrame::GetPatternWithChildren() {
+ // Do we have any children ourselves?
+ if (!mFrames.IsEmpty()) {
+ return this;
+ }
+
+ // No, see if we chain to someone who does
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return nullptr;
+ }
+
+ SVGPatternFrame* next = GetReferencedPattern();
+ if (!next) {
+ return nullptr;
+ }
+
+ return next->GetPatternWithChildren();
+}
+
+uint16_t SVGPatternFrame::GetEnumValue(uint32_t aIndex, nsIContent* aDefault) {
+ SVGAnimatedEnumeration& thisEnum =
+ static_cast<SVGPatternElement*>(GetContent())->mEnumAttributes[aIndex];
+
+ if (thisEnum.IsExplicitlySet()) {
+ return thisEnum.GetAnimValue();
+ }
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return static_cast<SVGPatternElement*>(aDefault)
+ ->mEnumAttributes[aIndex]
+ .GetAnimValue();
+ }
+
+ SVGPatternFrame* next = GetReferencedPattern();
+ return next ? next->GetEnumValue(aIndex, aDefault)
+ : static_cast<SVGPatternElement*>(aDefault)
+ ->mEnumAttributes[aIndex]
+ .GetAnimValue();
+}
+
+SVGAnimatedTransformList* SVGPatternFrame::GetPatternTransformList(
+ nsIContent* aDefault) {
+ SVGAnimatedTransformList* thisTransformList =
+ static_cast<SVGPatternElement*>(GetContent())->GetAnimatedTransformList();
+
+ if (thisTransformList && thisTransformList->IsExplicitlySet())
+ return thisTransformList;
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return static_cast<SVGPatternElement*>(aDefault)->mPatternTransform.get();
+ }
+
+ SVGPatternFrame* next = GetReferencedPattern();
+ return next ? next->GetPatternTransformList(aDefault)
+ : static_cast<SVGPatternElement*>(aDefault)
+ ->mPatternTransform.get();
+}
+
+gfxMatrix SVGPatternFrame::GetPatternTransform() {
+ SVGAnimatedTransformList* animTransformList =
+ GetPatternTransformList(GetContent());
+ if (!animTransformList) {
+ return gfxMatrix();
+ }
+
+ return animTransformList->GetAnimValue().GetConsolidationMatrix();
+}
+
+const SVGAnimatedViewBox& SVGPatternFrame::GetViewBox(nsIContent* aDefault) {
+ const SVGAnimatedViewBox& thisViewBox =
+ static_cast<SVGPatternElement*>(GetContent())->mViewBox;
+
+ if (thisViewBox.IsExplicitlySet()) {
+ return thisViewBox;
+ }
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return static_cast<SVGPatternElement*>(aDefault)->mViewBox;
+ }
+
+ SVGPatternFrame* next = GetReferencedPattern();
+ return next ? next->GetViewBox(aDefault)
+ : static_cast<SVGPatternElement*>(aDefault)->mViewBox;
+}
+
+const SVGAnimatedPreserveAspectRatio& SVGPatternFrame::GetPreserveAspectRatio(
+ nsIContent* aDefault) {
+ const SVGAnimatedPreserveAspectRatio& thisPar =
+ static_cast<SVGPatternElement*>(GetContent())->mPreserveAspectRatio;
+
+ if (thisPar.IsExplicitlySet()) {
+ return thisPar;
+ }
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return static_cast<SVGPatternElement*>(aDefault)->mPreserveAspectRatio;
+ }
+
+ SVGPatternFrame* next = GetReferencedPattern();
+ return next ? next->GetPreserveAspectRatio(aDefault)
+ : static_cast<SVGPatternElement*>(aDefault)->mPreserveAspectRatio;
+}
+
+const SVGAnimatedLength* SVGPatternFrame::GetLengthValue(uint32_t aIndex,
+ nsIContent* aDefault) {
+ const SVGAnimatedLength* thisLength =
+ &static_cast<SVGPatternElement*>(GetContent())->mLengthAttributes[aIndex];
+
+ if (thisLength->IsExplicitlySet()) {
+ return thisLength;
+ }
+
+ // Before we recurse, make sure we'll break reference loops and over long
+ // reference chains:
+ static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
+ AutoReferenceChainGuard refChainGuard(this, &mLoopFlag,
+ &sRefChainLengthCounter);
+ if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
+ // Break reference chain
+ return &static_cast<SVGPatternElement*>(aDefault)
+ ->mLengthAttributes[aIndex];
+ }
+
+ SVGPatternFrame* next = GetReferencedPattern();
+ return next ? next->GetLengthValue(aIndex, aDefault)
+ : &static_cast<SVGPatternElement*>(aDefault)
+ ->mLengthAttributes[aIndex];
+}
+
+// Private (helper) methods
+
+SVGPatternFrame* SVGPatternFrame::GetReferencedPattern() {
+ if (mNoHRefURI) {
+ return nullptr;
+ }
+
+ auto GetHref = [this](nsAString& aHref) {
+ SVGPatternElement* pattern =
+ static_cast<SVGPatternElement*>(this->GetContent());
+ if (pattern->mStringAttributes[SVGPatternElement::HREF].IsExplicitlySet()) {
+ pattern->mStringAttributes[SVGPatternElement::HREF].GetAnimValue(aHref,
+ pattern);
+ } else {
+ pattern->mStringAttributes[SVGPatternElement::XLINK_HREF].GetAnimValue(
+ aHref, pattern);
+ }
+ this->mNoHRefURI = aHref.IsEmpty();
+ };
+
+ nsIFrame* tframe = SVGObserverUtils::GetAndObserveTemplate(this, GetHref);
+ if (tframe) {
+ LayoutFrameType frameType = tframe->Type();
+ if (frameType == LayoutFrameType::SVGPattern) {
+ return static_cast<SVGPatternFrame*>(tframe);
+ }
+ // We don't call SVGObserverUtils::RemoveTemplateObserver and set
+ // `mNoHRefURI = false` here since we want to be invalidated if the ID
+ // specified by our href starts resolving to a different/valid element.
+ }
+
+ return nullptr;
+}
+
+gfxRect SVGPatternFrame::GetPatternRect(uint16_t aPatternUnits,
+ const gfxRect& aTargetBBox,
+ const Matrix& aTargetCTM,
+ nsIFrame* aTarget) {
+ // We need to initialize our box
+ float x, y, width, height;
+
+ // Get the pattern x,y,width, and height
+ const SVGAnimatedLength *tmpX, *tmpY, *tmpHeight, *tmpWidth;
+ tmpX = GetLengthValue(SVGPatternElement::ATTR_X);
+ tmpY = GetLengthValue(SVGPatternElement::ATTR_Y);
+ tmpHeight = GetLengthValue(SVGPatternElement::ATTR_HEIGHT);
+ tmpWidth = GetLengthValue(SVGPatternElement::ATTR_WIDTH);
+
+ if (aPatternUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ x = SVGUtils::ObjectSpace(aTargetBBox, tmpX);
+ y = SVGUtils::ObjectSpace(aTargetBBox, tmpY);
+ width = SVGUtils::ObjectSpace(aTargetBBox, tmpWidth);
+ height = SVGUtils::ObjectSpace(aTargetBBox, tmpHeight);
+ } else {
+ float scale = MaxExpansion(aTargetCTM);
+ x = SVGUtils::UserSpace(aTarget, tmpX) * scale;
+ y = SVGUtils::UserSpace(aTarget, tmpY) * scale;
+ width = SVGUtils::UserSpace(aTarget, tmpWidth) * scale;
+ height = SVGUtils::UserSpace(aTarget, tmpHeight) * scale;
+ }
+
+ return gfxRect(x, y, width, height);
+}
+
+gfxMatrix SVGPatternFrame::ConstructCTM(const SVGAnimatedViewBox& aViewBox,
+ uint16_t aPatternContentUnits,
+ uint16_t aPatternUnits,
+ const gfxRect& callerBBox,
+ const Matrix& callerCTM,
+ nsIFrame* aTarget) {
+ SVGViewportElement* ctx = nullptr;
+ nsIContent* targetContent = aTarget->GetContent();
+ gfxFloat scaleX, scaleY;
+
+ // The objectBoundingBox conversion must be handled in the CTM:
+ if (IncludeBBoxScale(aViewBox, aPatternContentUnits, aPatternUnits)) {
+ scaleX = callerBBox.Width();
+ scaleY = callerBBox.Height();
+ } else {
+ if (targetContent->IsSVGElement()) {
+ ctx = static_cast<SVGElement*>(targetContent)->GetCtx();
+ }
+ scaleX = scaleY = MaxExpansion(callerCTM);
+ }
+
+ if (!aViewBox.IsExplicitlySet()) {
+ return gfxMatrix(scaleX, 0.0, 0.0, scaleY, 0.0, 0.0);
+ }
+ const SVGViewBox& viewBox = aViewBox.GetAnimValue();
+
+ if (viewBox.height <= 0.0f || viewBox.width <= 0.0f) {
+ return gfxMatrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
+ }
+
+ float viewportWidth, viewportHeight;
+ if (targetContent->IsSVGElement()) {
+ // If we're dealing with an SVG target only retrieve the context once.
+ // Calling the nsIFrame* variant of GetAnimValue would look it up on
+ // every call.
+ viewportWidth =
+ GetLengthValue(SVGPatternElement::ATTR_WIDTH)->GetAnimValue(ctx);
+ viewportHeight =
+ GetLengthValue(SVGPatternElement::ATTR_HEIGHT)->GetAnimValue(ctx);
+ } else {
+ // No SVG target, call the nsIFrame* variant of GetAnimValue.
+ viewportWidth =
+ GetLengthValue(SVGPatternElement::ATTR_WIDTH)->GetAnimValue(aTarget);
+ viewportHeight =
+ GetLengthValue(SVGPatternElement::ATTR_HEIGHT)->GetAnimValue(aTarget);
+ }
+
+ if (viewportWidth <= 0.0f || viewportHeight <= 0.0f) {
+ return gfxMatrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
+ }
+
+ Matrix tm = SVGContentUtils::GetViewBoxTransform(
+ viewportWidth * scaleX, viewportHeight * scaleY, viewBox.x, viewBox.y,
+ viewBox.width, viewBox.height, GetPreserveAspectRatio());
+
+ return ThebesMatrix(tm);
+}
+
+//----------------------------------------------------------------------
+// SVGPaintServerFrame methods:
+already_AddRefed<gfxPattern> SVGPatternFrame::GetPaintServerPattern(
+ nsIFrame* aSource, const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix, StyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ float aGraphicOpacity, imgDrawingParams& aImgParams,
+ const gfxRect* aOverrideBounds) {
+ if (aGraphicOpacity == 0.0f) {
+ return do_AddRef(new gfxPattern(DeviceColor()));
+ }
+
+ // Paint it!
+ Matrix pMatrix;
+ RefPtr<SourceSurface> surface =
+ PaintPattern(aDrawTarget, &pMatrix, ToMatrix(aContextMatrix), aSource,
+ aFillOrStroke, aGraphicOpacity, aOverrideBounds, aImgParams);
+
+ if (!surface) {
+ return nullptr;
+ }
+
+ RefPtr<gfxPattern> pattern = new gfxPattern(surface, pMatrix);
+
+ if (!pattern) {
+ return nullptr;
+ }
+
+ pattern->SetExtend(ExtendMode::REPEAT);
+ return pattern.forget();
+}
+
+} // namespace mozilla
+
+// -------------------------------------------------------------------------
+// Public functions
+// -------------------------------------------------------------------------
+
+nsIFrame* NS_NewSVGPatternFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGPatternFrame(aStyle, aPresShell->GetPresContext());
+}
diff --git a/layout/svg/SVGPatternFrame.h b/layout/svg/SVGPatternFrame.h
new file mode 100644
index 0000000000..4110b51289
--- /dev/null
+++ b/layout/svg/SVGPatternFrame.h
@@ -0,0 +1,136 @@
+/* -*- 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 LAYOUT_SVG_SVGPATTERNFRAME_H_
+#define LAYOUT_SVG_SVGPATTERNFRAME_H_
+
+#include "mozilla/Attributes.h"
+#include "gfxMatrix.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SVGPaintServerFrame.h"
+#include "mozilla/UniquePtr.h"
+
+class nsIFrame;
+
+namespace mozilla {
+class PresShell;
+class SVGAnimatedLength;
+class SVGAnimatedPreserveAspectRatio;
+class SVGAnimatedTransformList;
+class SVGAnimatedViewBox;
+class SVGGeometryFrame;
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGPatternFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGPatternFrame final : public SVGPaintServerFrame {
+ using SourceSurface = gfx::SourceSurface;
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGPatternFrame)
+ NS_DECL_QUERYFRAME
+
+ friend nsIFrame* ::NS_NewSVGPatternFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ explicit SVGPatternFrame(ComputedStyle* aStyle, nsPresContext* aPresContext);
+
+ // SVGPaintServerFrame methods:
+ already_AddRefed<gfxPattern> GetPaintServerPattern(
+ nsIFrame* aSource, const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix, StyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ float aGraphicOpacity, imgDrawingParams& aImgParams,
+ const gfxRect* aOverrideBounds) override;
+
+ public:
+ // SVGContainerFrame methods:
+ gfxMatrix GetCanvasTM() override;
+
+ // nsIFrame interface:
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+#ifdef DEBUG
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGPattern"_ns, aResult);
+ }
+#endif // DEBUG
+
+ protected:
+ /**
+ * Parses this frame's href and - if it references another pattern - returns
+ * it. It also makes this frame a rendering observer of the specified ID.
+ */
+ SVGPatternFrame* GetReferencedPattern();
+
+ // Accessors to lookup pattern attributes
+ uint16_t GetEnumValue(uint32_t aIndex, nsIContent* aDefault);
+ uint16_t GetEnumValue(uint32_t aIndex) {
+ return GetEnumValue(aIndex, mContent);
+ }
+ SVGAnimatedTransformList* GetPatternTransformList(nsIContent* aDefault);
+ gfxMatrix GetPatternTransform();
+ const SVGAnimatedViewBox& GetViewBox(nsIContent* aDefault);
+ const SVGAnimatedViewBox& GetViewBox() { return GetViewBox(mContent); }
+ const SVGAnimatedPreserveAspectRatio& GetPreserveAspectRatio(
+ nsIContent* aDefault);
+ const SVGAnimatedPreserveAspectRatio& GetPreserveAspectRatio() {
+ return GetPreserveAspectRatio(mContent);
+ }
+ const SVGAnimatedLength* GetLengthValue(uint32_t aIndex,
+ nsIContent* aDefault);
+ const SVGAnimatedLength* GetLengthValue(uint32_t aIndex) {
+ return GetLengthValue(aIndex, mContent);
+ }
+
+ already_AddRefed<SourceSurface> PaintPattern(
+ const DrawTarget* aDrawTarget, Matrix* patternMatrix,
+ const Matrix& aContextMatrix, nsIFrame* aSource,
+ StyleSVGPaint nsStyleSVG::*aFillOrStroke, float aGraphicOpacity,
+ const gfxRect* aOverrideBounds, imgDrawingParams& aImgParams);
+
+ /**
+ * A <pattern> element may reference another <pattern> element using
+ * xlink:href and, if it doesn't have any child content of its own, then it
+ * will "inherit" the children of the referenced pattern (which may itself be
+ * inheriting its children if it references another <pattern>). This
+ * function returns this SVGPatternFrame or the first pattern along the
+ * reference chain (if there is one) to have children.
+ */
+ SVGPatternFrame* GetPatternWithChildren();
+
+ gfxRect GetPatternRect(uint16_t aPatternUnits, const gfxRect& bbox,
+ const Matrix& aTargetCTM, nsIFrame* aTarget);
+ gfxMatrix ConstructCTM(const SVGAnimatedViewBox& aViewBox,
+ uint16_t aPatternContentUnits, uint16_t aPatternUnits,
+ const gfxRect& callerBBox, const Matrix& callerCTM,
+ nsIFrame* aTarget);
+
+ private:
+ // this is a *temporary* reference to the frame of the element currently
+ // referencing our pattern. This must be temporary because different
+ // referencing frames will all reference this one frame
+ SVGGeometryFrame* mSource;
+ UniquePtr<gfxMatrix> mCTM;
+
+ protected:
+ // This flag is used to detect loops in xlink:href processing
+ bool mLoopFlag;
+ bool mNoHRefURI;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGPATTERNFRAME_H_
diff --git a/layout/svg/SVGStopFrame.cpp b/layout/svg/SVGStopFrame.cpp
new file mode 100644
index 0000000000..b6a3ab69a6
--- /dev/null
+++ b/layout/svg/SVGStopFrame.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/. */
+
+// Keep in (case-insensitive) order:
+#include "nsContainerFrame.h"
+#include "nsIFrame.h"
+#include "nsGkAtoms.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGGradientFrame.h"
+#include "mozilla/SVGObserverUtils.h"
+
+// This is a very simple frame whose only purpose is to capture style change
+// events and propagate them to the parent. Most of the heavy lifting is done
+// within the SVGGradientFrame, which is the parent for this frame
+
+nsIFrame* NS_NewSVGStopFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGStopFrame : public nsIFrame {
+ friend nsIFrame* ::NS_NewSVGStopFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ protected:
+ explicit SVGStopFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsIFrame(aStyle, aPresContext, kClassID) {
+ AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY);
+ }
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGStopFrame)
+
+ // nsIFrame interface:
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override {}
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ bool IsFrameOfType(uint32_t aFlags) const override {
+ if (aFlags & eSupportsContainLayoutAndPaint) {
+ return false;
+ }
+
+ return nsIFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGStop"_ns, aResult);
+ }
+#endif
+};
+
+//----------------------------------------------------------------------
+// Implementation
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGStopFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods:
+
+#ifdef DEBUG
+void SVGStopFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::stop),
+ "Content is not a stop element");
+
+ nsIFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsresult SVGStopFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType) {
+ if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::offset) {
+ MOZ_ASSERT(
+ static_cast<SVGGradientFrame*>(do_QueryFrame(GetParent())),
+ "Observers observe the gradient, so that's what we must invalidate");
+ SVGObserverUtils::InvalidateDirectRenderingObservers(GetParent());
+ }
+
+ return nsIFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+} // namespace mozilla
+
+// -------------------------------------------------------------------------
+// Public functions
+// -------------------------------------------------------------------------
+
+nsIFrame* NS_NewSVGStopFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGStopFrame(aStyle, aPresShell->GetPresContext());
+}
diff --git a/layout/svg/SVGSwitchFrame.cpp b/layout/svg/SVGSwitchFrame.cpp
new file mode 100644
index 0000000000..e846b1eb0f
--- /dev/null
+++ b/layout/svg/SVGSwitchFrame.cpp
@@ -0,0 +1,289 @@
+/* -*- 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/. */
+
+// Keep in (case-insensitive) order:
+#include "gfxRect.h"
+#include "SVGGFrame.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGContainerFrame.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGTextFrame.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/SVGSwitchElement.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+nsIFrame* NS_NewSVGSwitchFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGSwitchFrame final : public SVGGFrame {
+ friend nsIFrame* ::NS_NewSVGSwitchFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ protected:
+ explicit SVGSwitchFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : SVGGFrame(aStyle, aPresContext, kClassID) {}
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGSwitchFrame)
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGSwitch"_ns, aResult);
+ }
+#endif
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+ // ISVGDisplayableFrame interface:
+ virtual void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect = nullptr) override;
+ nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
+ void ReflowSVG() override;
+ virtual SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace,
+ uint32_t aFlags) override;
+
+ private:
+ nsIFrame* GetActiveChildFrame();
+ void ReflowAllSVGTextFramesInsideNonActiveChildren(nsIFrame* aActiveChild);
+ static void AlwaysReflowSVGTextFrameDoForOneKid(nsIFrame* aKid);
+};
+
+} // namespace mozilla
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsIFrame* NS_NewSVGSwitchFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGSwitchFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGSwitchFrame)
+
+#ifdef DEBUG
+void SVGSwitchFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svgSwitch),
+ "Content is not an SVG switch");
+
+ SVGGFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+void SVGSwitchFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ nsIFrame* kid = GetActiveChildFrame();
+ if (kid) {
+ BuildDisplayListForChild(aBuilder, kid, aLists);
+ }
+}
+
+void SVGSwitchFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect) {
+ NS_ASSERTION(
+ !NS_SVGDisplayListPaintingEnabled() || (mState & NS_FRAME_IS_NONDISPLAY),
+ "If display lists are enabled, only painting of non-display "
+ "SVG should take this code path");
+
+ if (StyleEffects()->mOpacity == 0.0) {
+ return;
+ }
+
+ nsIFrame* kid = GetActiveChildFrame();
+ if (kid) {
+ gfxMatrix tm = aTransform;
+ if (kid->GetContent()->IsSVGElement()) {
+ tm = SVGUtils::GetTransformMatrixInUserSpace(kid) * tm;
+ }
+ SVGUtils::PaintFrameWithEffects(kid, aContext, tm, aImgParams, aDirtyRect);
+ }
+}
+
+nsIFrame* SVGSwitchFrame::GetFrameForPoint(const gfxPoint& aPoint) {
+ NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "If display lists are enabled, only hit-testing of non-display "
+ "SVG should take this code path");
+
+ nsIFrame* kid = GetActiveChildFrame();
+ ISVGDisplayableFrame* svgFrame = do_QueryFrame(kid);
+ if (svgFrame) {
+ // Transform the point from our SVG user space to our child's.
+ gfxPoint point = aPoint;
+ gfxMatrix m =
+ static_cast<const SVGElement*>(GetContent())
+ ->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace);
+ m = static_cast<const SVGElement*>(kid->GetContent())
+ ->PrependLocalTransformsTo(m, eUserSpaceToParent);
+ if (!m.IsIdentity()) {
+ if (!m.Invert()) {
+ return nullptr;
+ }
+ point = m.TransformPoint(point);
+ }
+ return svgFrame->GetFrameForPoint(point);
+ }
+
+ return nullptr;
+}
+
+static bool shouldReflowSVGTextFrameInside(nsIFrame* aFrame) {
+ return aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer) ||
+ aFrame->IsSVGForeignObjectFrame() ||
+ !aFrame->IsFrameOfType(nsIFrame::eSVG);
+}
+
+void SVGSwitchFrame::AlwaysReflowSVGTextFrameDoForOneKid(nsIFrame* aKid) {
+ if (!aKid->IsSubtreeDirty()) {
+ return;
+ }
+
+ if (aKid->IsSVGTextFrame()) {
+ MOZ_ASSERT(!aKid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
+ "A non-display SVGTextFrame directly contained in a display "
+ "container?");
+ static_cast<SVGTextFrame*>(aKid)->ReflowSVG();
+ } else if (shouldReflowSVGTextFrameInside(aKid)) {
+ if (!aKid->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ for (nsIFrame* kid : aKid->PrincipalChildList()) {
+ AlwaysReflowSVGTextFrameDoForOneKid(kid);
+ }
+ } else {
+ // This child is in a nondisplay context, something like:
+ // <switch>
+ // ...
+ // <g><mask><text></text></mask></g>
+ // </switch>
+ // We should not call ReflowSVG on it.
+ SVGContainerFrame::ReflowSVGNonDisplayText(aKid);
+ }
+ }
+}
+
+void SVGSwitchFrame::ReflowAllSVGTextFramesInsideNonActiveChildren(
+ nsIFrame* aActiveChild) {
+ for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
+ if (aActiveChild == kid) {
+ continue;
+ }
+
+ AlwaysReflowSVGTextFrameDoForOneKid(kid);
+ }
+}
+
+void SVGSwitchFrame::ReflowSVG() {
+ NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
+ "This call is probably a wasteful mistake");
+
+ MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
+ "ReflowSVG mechanism not designed for this");
+
+ if (!SVGUtils::NeedsReflowSVG(this)) {
+ return;
+ }
+
+ // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame,
+ // then our outer-<svg> has previously had its initial reflow. In that case
+ // we need to make sure that that bit has been removed from ourself _before_
+ // recursing over our children to ensure that they know too. Otherwise, we
+ // need to remove it _after_ recursing over our children so that they know
+ // the initial reflow is currently underway.
+
+ bool isFirstReflow = (mState & NS_FRAME_FIRST_REFLOW);
+
+ bool outerSVGHasHadFirstReflow =
+ !GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
+
+ if (outerSVGHasHadFirstReflow) {
+ RemoveStateBits(NS_FRAME_FIRST_REFLOW); // tell our children
+ }
+
+ OverflowAreas overflowRects;
+
+ nsIFrame* child = GetActiveChildFrame();
+ ReflowAllSVGTextFramesInsideNonActiveChildren(child);
+
+ ISVGDisplayableFrame* svgChild = do_QueryFrame(child);
+ if (svgChild) {
+ MOZ_ASSERT(!child->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
+ "Check for this explicitly in the |if|, then");
+ svgChild->ReflowSVG();
+
+ // We build up our child frame overflows here instead of using
+ // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same
+ // frame list, and we're iterating over that list now anyway.
+ ConsiderChildOverflow(overflowRects, child);
+ } else if (child && shouldReflowSVGTextFrameInside(child)) {
+ MOZ_ASSERT(child->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) ||
+ !child->IsFrameOfType(nsIFrame::eSVG),
+ "Check for this explicitly in the |if|, then");
+ ReflowSVGNonDisplayText(child);
+ }
+
+ if (isFirstReflow) {
+ // Make sure we have our filter property (if any) before calling
+ // FinishAndStoreOverflow (subsequent filter changes are handled off
+ // nsChangeHint_UpdateEffects):
+ SVGObserverUtils::UpdateEffects(this);
+ }
+
+ FinishAndStoreOverflow(overflowRects, mRect.Size());
+
+ // Remove state bits after FinishAndStoreOverflow so that it doesn't
+ // invalidate on first reflow:
+ RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+SVGBBox SVGSwitchFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace,
+ uint32_t aFlags) {
+ nsIFrame* kid = GetActiveChildFrame();
+ ISVGDisplayableFrame* svgKid = do_QueryFrame(kid);
+ if (svgKid) {
+ nsIContent* content = kid->GetContent();
+ gfxMatrix transform = ThebesMatrix(aToBBoxUserspace);
+ if (content->IsSVGElement()) {
+ transform = static_cast<SVGElement*>(content)->PrependLocalTransformsTo(
+ {}, eChildToUserSpace) *
+ SVGUtils::GetTransformMatrixInUserSpace(kid) * transform;
+ }
+ return svgKid->GetBBoxContribution(ToMatrix(transform), aFlags);
+ }
+ return SVGBBox();
+}
+
+nsIFrame* SVGSwitchFrame::GetActiveChildFrame() {
+ nsIContent* activeChild =
+ static_cast<dom::SVGSwitchElement*>(GetContent())->GetActiveChild();
+
+ if (activeChild) {
+ for (nsIFrame* kid = mFrames.FirstChild(); kid;
+ kid = kid->GetNextSibling()) {
+ if (activeChild == kid->GetContent()) {
+ return kid;
+ }
+ }
+ }
+ return nullptr;
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGSymbolFrame.cpp b/layout/svg/SVGSymbolFrame.cpp
new file mode 100644
index 0000000000..63cb31af5b
--- /dev/null
+++ b/layout/svg/SVGSymbolFrame.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/. */
+
+// Main header first:
+#include "SVGSymbolFrame.h"
+
+#include "mozilla/PresShell.h"
+
+nsIFrame* NS_NewSVGSymbolFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGSymbolFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGSymbolFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods
+
+NS_QUERYFRAME_HEAD(SVGSymbolFrame)
+ NS_QUERYFRAME_ENTRY(SVGSymbolFrame)
+NS_QUERYFRAME_TAIL_INHERITING(SVGViewportFrame)
+
+#ifdef DEBUG
+void SVGSymbolFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::symbol),
+ "Content is not an SVG 'symbol' element!");
+
+ SVGViewportFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+} // namespace mozilla
diff --git a/layout/svg/SVGSymbolFrame.h b/layout/svg/SVGSymbolFrame.h
new file mode 100644
index 0000000000..044e70eec7
--- /dev/null
+++ b/layout/svg/SVGSymbolFrame.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 LAYOUT_SVG_SVGSYMBOLFRAME_H_
+#define LAYOUT_SVG_SVGSYMBOLFRAME_H_
+
+#include "SVGViewportFrame.h"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGSymbolFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGSymbolFrame final : public SVGViewportFrame {
+ friend nsIFrame* ::NS_NewSVGSymbolFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ protected:
+ explicit SVGSymbolFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : SVGViewportFrame(aStyle, aPresContext, kClassID) {}
+
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(SVGSymbolFrame)
+
+#ifdef DEBUG
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGSymbol"_ns, aResult);
+ }
+#endif
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGSYMBOLFRAME_H_
diff --git a/layout/svg/SVGTextFrame.cpp b/layout/svg/SVGTextFrame.cpp
new file mode 100644
index 0000000000..be8248ab68
--- /dev/null
+++ b/layout/svg/SVGTextFrame.cpp
@@ -0,0 +1,5413 @@
+/* -*- 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:
+#include "SVGTextFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "DOMSVGPoint.h"
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxFont.h"
+#include "gfxSkipChars.h"
+#include "gfxTypes.h"
+#include "gfxUtils.h"
+#include "LookAndFeel.h"
+#include "nsAlgorithm.h"
+#include "nsBidiPresUtils.h"
+#include "nsBlockFrame.h"
+#include "nsCaret.h"
+#include "nsContentUtils.h"
+#include "nsGkAtoms.h"
+#include "nsQuickSort.h"
+#include "SVGPaintServerFrame.h"
+#include "nsTArray.h"
+#include "nsTextFrame.h"
+#include "SVGAnimatedNumberList.h"
+#include "SVGContentUtils.h"
+#include "SVGContextPaint.h"
+#include "SVGLengthList.h"
+#include "SVGNumberList.h"
+#include "nsLayoutUtils.h"
+#include "nsFrameSelection.h"
+#include "nsStyleStructInlines.h"
+#include "mozilla/Likely.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGOuterSVGFrame.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/DOMPointBinding.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/SVGGeometryElement.h"
+#include "mozilla/dom/SVGRect.h"
+#include "mozilla/dom/SVGTextContentElementBinding.h"
+#include "mozilla/dom/SVGTextPathElement.h"
+#include "mozilla/dom/Text.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PatternHelpers.h"
+#include "nsDisplayList.h"
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+using namespace mozilla::dom;
+using namespace mozilla::dom::SVGTextContentElement_Binding;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+namespace mozilla {
+
+// ============================================================================
+// Utility functions
+
+/**
+ * Using the specified gfxSkipCharsIterator, converts an offset and length
+ * in original char indexes to skipped char indexes.
+ *
+ * @param aIterator The gfxSkipCharsIterator to use for the conversion.
+ * @param aOriginalOffset The original offset.
+ * @param aOriginalLength The original length.
+ */
+static gfxTextRun::Range ConvertOriginalToSkipped(
+ gfxSkipCharsIterator& aIterator, uint32_t aOriginalOffset,
+ uint32_t aOriginalLength) {
+ uint32_t start = aIterator.ConvertOriginalToSkipped(aOriginalOffset);
+ aIterator.AdvanceOriginal(aOriginalLength);
+ return gfxTextRun::Range(start, aIterator.GetSkippedOffset());
+}
+
+/**
+ * Converts an nsPoint from app units to user space units using the specified
+ * nsPresContext and returns it as a gfxPoint.
+ */
+static gfxPoint AppUnitsToGfxUnits(const nsPoint& aPoint,
+ const nsPresContext* aContext) {
+ return gfxPoint(aContext->AppUnitsToGfxUnits(aPoint.x),
+ aContext->AppUnitsToGfxUnits(aPoint.y));
+}
+
+/**
+ * Converts a gfxRect that is in app units to CSS pixels using the specified
+ * nsPresContext and returns it as a gfxRect.
+ */
+static gfxRect AppUnitsToFloatCSSPixels(const gfxRect& aRect,
+ const nsPresContext* aContext) {
+ return gfxRect(nsPresContext::AppUnitsToFloatCSSPixels(aRect.x),
+ nsPresContext::AppUnitsToFloatCSSPixels(aRect.y),
+ nsPresContext::AppUnitsToFloatCSSPixels(aRect.width),
+ nsPresContext::AppUnitsToFloatCSSPixels(aRect.height));
+}
+
+/**
+ * Returns whether a gfxPoint lies within a gfxRect.
+ */
+static bool Inside(const gfxRect& aRect, const gfxPoint& aPoint) {
+ return aPoint.x >= aRect.x && aPoint.x < aRect.XMost() &&
+ aPoint.y >= aRect.y && aPoint.y < aRect.YMost();
+}
+
+/**
+ * Gets the measured ascent and descent of the text in the given nsTextFrame
+ * in app units.
+ *
+ * @param aFrame The text frame.
+ * @param aAscent The ascent in app units (output).
+ * @param aDescent The descent in app units (output).
+ */
+static void GetAscentAndDescentInAppUnits(nsTextFrame* aFrame,
+ gfxFloat& aAscent,
+ gfxFloat& aDescent) {
+ gfxSkipCharsIterator it = aFrame->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated);
+
+ gfxTextRun::Range range = ConvertOriginalToSkipped(
+ it, aFrame->GetContentOffset(), aFrame->GetContentLength());
+
+ textRun->GetLineHeightMetrics(range, aAscent, aDescent);
+}
+
+/**
+ * Updates an interval by intersecting it with another interval.
+ * The intervals are specified using a start index and a length.
+ */
+static void IntersectInterval(uint32_t& aStart, uint32_t& aLength,
+ uint32_t aStartOther, uint32_t aLengthOther) {
+ uint32_t aEnd = aStart + aLength;
+ uint32_t aEndOther = aStartOther + aLengthOther;
+
+ if (aStartOther >= aEnd || aStart >= aEndOther) {
+ aLength = 0;
+ } else {
+ if (aStartOther >= aStart) aStart = aStartOther;
+ aLength = std::min(aEnd, aEndOther) - aStart;
+ }
+}
+
+/**
+ * Intersects an interval as IntersectInterval does but by taking
+ * the offset and length of the other interval from a
+ * nsTextFrame::TrimmedOffsets object.
+ */
+static void TrimOffsets(uint32_t& aStart, uint32_t& aLength,
+ const nsTextFrame::TrimmedOffsets& aTrimmedOffsets) {
+ IntersectInterval(aStart, aLength, aTrimmedOffsets.mStart,
+ aTrimmedOffsets.mLength);
+}
+
+/**
+ * Returns the closest ancestor-or-self node that is not an SVG <a>
+ * element.
+ */
+static nsIContent* GetFirstNonAAncestor(nsIContent* aContent) {
+ while (aContent && aContent->IsSVGElement(nsGkAtoms::a)) {
+ aContent = aContent->GetParent();
+ }
+ return aContent;
+}
+
+/**
+ * Returns whether the given node is a text content element[1], taking into
+ * account whether it has a valid parent.
+ *
+ * For example, in:
+ *
+ * <svg xmlns="http://www.w3.org/2000/svg">
+ * <text><a/><text/></text>
+ * <tspan/>
+ * </svg>
+ *
+ * true would be returned for the outer <text> element and the <a> element,
+ * and false for the inner <text> element (since a <text> is not allowed
+ * to be a child of another <text>) and the <tspan> element (because it
+ * must be inside a <text> subtree).
+ *
+ * Note that we don't support the <tref> element yet and this function
+ * returns false for it.
+ *
+ * [1] https://svgwg.org/svg2-draft/intro.html#TermTextContentElement
+ */
+static bool IsTextContentElement(nsIContent* aContent) {
+ if (aContent->IsSVGElement(nsGkAtoms::text)) {
+ nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent());
+ return !parent || !IsTextContentElement(parent);
+ }
+
+ if (aContent->IsSVGElement(nsGkAtoms::textPath)) {
+ nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent());
+ return parent && parent->IsSVGElement(nsGkAtoms::text);
+ }
+
+ return aContent->IsAnyOfSVGElements(nsGkAtoms::a, nsGkAtoms::tspan);
+}
+
+/**
+ * Returns whether the specified frame is an nsTextFrame that has some text
+ * content.
+ */
+static bool IsNonEmptyTextFrame(nsIFrame* aFrame) {
+ nsTextFrame* textFrame = do_QueryFrame(aFrame);
+ if (!textFrame) {
+ return false;
+ }
+
+ return textFrame->GetContentLength() != 0;
+}
+
+/**
+ * Takes an nsIFrame and if it is a text frame that has some text content,
+ * returns it as an nsTextFrame and its corresponding Text.
+ *
+ * @param aFrame The frame to look at.
+ * @param aTextFrame aFrame as an nsTextFrame (output).
+ * @param aTextNode The Text content of aFrame (output).
+ * @return true if aFrame is a non-empty text frame, false otherwise.
+ */
+static bool GetNonEmptyTextFrameAndNode(nsIFrame* aFrame,
+ nsTextFrame*& aTextFrame,
+ Text*& aTextNode) {
+ nsTextFrame* text = do_QueryFrame(aFrame);
+ bool isNonEmptyTextFrame = text && text->GetContentLength() != 0;
+
+ if (isNonEmptyTextFrame) {
+ nsIContent* content = text->GetContent();
+ NS_ASSERTION(content && content->IsText(),
+ "unexpected content type for nsTextFrame");
+
+ Text* node = content->AsText();
+ MOZ_ASSERT(node->TextLength() != 0,
+ "frame's GetContentLength() should be 0 if the text node "
+ "has no content");
+
+ aTextFrame = text;
+ aTextNode = node;
+ }
+
+ MOZ_ASSERT(IsNonEmptyTextFrame(aFrame) == isNonEmptyTextFrame,
+ "our logic should agree with IsNonEmptyTextFrame");
+ return isNonEmptyTextFrame;
+}
+
+/**
+ * Returns whether the specified atom is for one of the five
+ * glyph positioning attributes that can appear on SVG text
+ * elements -- x, y, dx, dy or rotate.
+ */
+static bool IsGlyphPositioningAttribute(nsAtom* aAttribute) {
+ return aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y ||
+ aAttribute == nsGkAtoms::dx || aAttribute == nsGkAtoms::dy ||
+ aAttribute == nsGkAtoms::rotate;
+}
+
+/**
+ * Returns the position in app units of a given baseline (using an
+ * SVG dominant-baseline property value) for a given nsTextFrame.
+ *
+ * @param aFrame The text frame to inspect.
+ * @param aTextRun The text run of aFrame.
+ * @param aDominantBaseline The dominant-baseline value to use.
+ */
+static nscoord GetBaselinePosition(nsTextFrame* aFrame, gfxTextRun* aTextRun,
+ StyleDominantBaseline aDominantBaseline,
+ float aFontSizeScaleFactor) {
+ WritingMode writingMode = aFrame->GetWritingMode();
+ gfxFloat ascent, descent;
+ aTextRun->GetLineHeightMetrics(ascent, descent);
+
+ auto convertIfVerticalRL = [&](gfxFloat dominantBaseline) {
+ return writingMode.IsVerticalRL() ? ascent + descent - dominantBaseline
+ : dominantBaseline;
+ };
+
+ switch (aDominantBaseline) {
+ case StyleDominantBaseline::Hanging:
+ return convertIfVerticalRL(ascent * 0.2);
+ case StyleDominantBaseline::TextBeforeEdge:
+ return convertIfVerticalRL(0);
+
+ case StyleDominantBaseline::Alphabetic:
+ return writingMode.IsVerticalRL()
+ ? ascent * 0.5
+ : aFrame->GetLogicalBaseline(writingMode);
+
+ case StyleDominantBaseline::Auto:
+ return convertIfVerticalRL(aFrame->GetLogicalBaseline(writingMode));
+
+ case StyleDominantBaseline::Middle:
+ return convertIfVerticalRL(aFrame->GetLogicalBaseline(writingMode) -
+ SVGContentUtils::GetFontXHeight(aFrame) / 2.0 *
+ AppUnitsPerCSSPixel() *
+ aFontSizeScaleFactor);
+
+ case StyleDominantBaseline::TextAfterEdge:
+ case StyleDominantBaseline::Ideographic:
+ return writingMode.IsVerticalLR() ? 0 : ascent + descent;
+
+ case StyleDominantBaseline::Central:
+ return (ascent + descent) / 2.0;
+ case StyleDominantBaseline::Mathematical:
+ return convertIfVerticalRL(ascent / 2.0);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("unexpected dominant-baseline value");
+ return convertIfVerticalRL(aFrame->GetLogicalBaseline(writingMode));
+}
+
+/**
+ * Truncates an array to be at most the length of another array.
+ *
+ * @param aArrayToTruncate The array to truncate.
+ * @param aReferenceArray The array whose length will be used to truncate
+ * aArrayToTruncate to.
+ */
+template <typename T, typename U>
+static void TruncateTo(nsTArray<T>& aArrayToTruncate,
+ const nsTArray<U>& aReferenceArray) {
+ uint32_t length = aReferenceArray.Length();
+ if (aArrayToTruncate.Length() > length) {
+ aArrayToTruncate.TruncateLength(length);
+ }
+}
+
+/**
+ * Asserts that the anonymous block child of the SVGTextFrame has been
+ * reflowed (or does not exist). Returns null if the child has not been
+ * reflowed, and the frame otherwise.
+ *
+ * We check whether the kid has been reflowed and not the frame itself
+ * since we sometimes need to call this function during reflow, after the
+ * kid has been reflowed but before we have cleared the dirty bits on the
+ * frame itself.
+ */
+static SVGTextFrame* FrameIfAnonymousChildReflowed(SVGTextFrame* aFrame) {
+ MOZ_ASSERT(aFrame, "aFrame must not be null");
+ nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
+ if (kid->IsSubtreeDirty()) {
+ MOZ_ASSERT(false, "should have already reflowed the anonymous block child");
+ return nullptr;
+ }
+ return aFrame;
+}
+
+static double GetContextScale(const gfxMatrix& aMatrix) {
+ // The context scale is the ratio of the length of the transformed
+ // diagonal vector (1,1) to the length of the untransformed diagonal
+ // (which is sqrt(2)).
+ gfxPoint p = aMatrix.TransformPoint(gfxPoint(1, 1)) -
+ aMatrix.TransformPoint(gfxPoint(0, 0));
+ return SVGContentUtils::ComputeNormalizedHypotenuse(p.x, p.y);
+}
+
+// ============================================================================
+// Utility classes
+
+// ----------------------------------------------------------------------------
+// TextRenderedRun
+
+/**
+ * A run of text within a single nsTextFrame whose glyphs can all be painted
+ * with a single call to nsTextFrame::PaintText. A text rendered run can
+ * be created for a sequence of two or more consecutive glyphs as long as:
+ *
+ * - Only the first glyph has (or none of the glyphs have) been positioned
+ * with SVG text positioning attributes
+ * - All of the glyphs have zero rotation
+ * - The glyphs are not on a text path
+ * - The glyphs correspond to content within the one nsTextFrame
+ *
+ * A TextRenderedRunIterator produces TextRenderedRuns required for painting a
+ * whole SVGTextFrame.
+ */
+struct TextRenderedRun {
+ using Range = gfxTextRun::Range;
+
+ /**
+ * Constructs a TextRenderedRun that is uninitialized except for mFrame
+ * being null.
+ */
+ TextRenderedRun() : mFrame(nullptr) {}
+
+ /**
+ * Constructs a TextRenderedRun with all of the information required to
+ * paint it. See the comments documenting the member variables below
+ * for descriptions of the arguments.
+ */
+ TextRenderedRun(nsTextFrame* aFrame, const gfxPoint& aPosition,
+ float aLengthAdjustScaleFactor, double aRotate,
+ float aFontSizeScaleFactor, nscoord aBaseline,
+ uint32_t aTextFrameContentOffset,
+ uint32_t aTextFrameContentLength,
+ uint32_t aTextElementCharIndex)
+ : mFrame(aFrame),
+ mPosition(aPosition),
+ mLengthAdjustScaleFactor(aLengthAdjustScaleFactor),
+ mRotate(static_cast<float>(aRotate)),
+ mFontSizeScaleFactor(aFontSizeScaleFactor),
+ mBaseline(aBaseline),
+ mTextFrameContentOffset(aTextFrameContentOffset),
+ mTextFrameContentLength(aTextFrameContentLength),
+ mTextElementCharIndex(aTextElementCharIndex) {}
+
+ /**
+ * Returns the text run for the text frame that this rendered run is part of.
+ */
+ gfxTextRun* GetTextRun() const {
+ mFrame->EnsureTextRun(nsTextFrame::eInflated);
+ return mFrame->GetTextRun(nsTextFrame::eInflated);
+ }
+
+ /**
+ * Returns whether this rendered run is RTL.
+ */
+ bool IsRightToLeft() const { return GetTextRun()->IsRightToLeft(); }
+
+ /**
+ * Returns whether this rendered run is vertical.
+ */
+ bool IsVertical() const { return GetTextRun()->IsVertical(); }
+
+ /**
+ * Returns the transform that converts from a <text> element's user space into
+ * the coordinate space that rendered runs can be painted directly in.
+ *
+ * The difference between this method and
+ * GetTransformFromRunUserSpaceToUserSpace is that when calling in to
+ * nsTextFrame::PaintText, it will already take into account any left clip
+ * edge (that is, it doesn't just apply a visual clip to the rendered text, it
+ * shifts the glyphs over so that they are painted with their left edge at the
+ * x coordinate passed in to it). Thus we need to account for this in our
+ * transform.
+ *
+ *
+ * Assume that we have:
+ *
+ * <text x="100" y="100" rotate="0 0 1 0 0 * 1">abcdef</text>.
+ *
+ * This would result in four text rendered runs:
+ *
+ * - one for "ab"
+ * - one for "c"
+ * - one for "de"
+ * - one for "f"
+ *
+ * Assume now that we are painting the third TextRenderedRun. It will have
+ * a left clip edge that is the sum of the advances of "abc", and it will
+ * have a right clip edge that is the advance of "f". In
+ * SVGTextFrame::PaintSVG(), we pass in nsPoint() (i.e., the origin)
+ * as the point at which to paint the text frame, and we pass in the
+ * clip edge values. The nsTextFrame will paint the substring of its
+ * text such that the top-left corner of the "d"'s glyph cell will be at
+ * (0, 0) in the current coordinate system.
+ *
+ * Thus, GetTransformFromUserSpaceForPainting must return a transform from
+ * whatever user space the <text> element is in to a coordinate space in
+ * device pixels (as that's what nsTextFrame works in) where the origin is at
+ * the same position as our user space mPositions[i].mPosition value for
+ * the "d" glyph, which will be (100 + userSpaceAdvance("abc"), 100).
+ * The translation required to do this (ignoring the scale to get from
+ * user space to device pixels, and ignoring the
+ * (100 + userSpaceAdvance("abc"), 100) translation) is:
+ *
+ * (-leftEdge, -baseline)
+ *
+ * where baseline is the distance between the baseline of the text and the top
+ * edge of the nsTextFrame. We translate by -leftEdge horizontally because
+ * the nsTextFrame will already shift the glyphs over by that amount and start
+ * painting glyphs at x = 0. We translate by -baseline vertically so that
+ * painting the top edges of the glyphs at y = 0 will result in their
+ * baselines being at our desired y position.
+ *
+ *
+ * Now for an example with RTL text. Assume our content is now
+ * <text x="100" y="100" rotate="0 0 1 0 0 1">WERBEH</text>. We'd have
+ * the following text rendered runs:
+ *
+ * - one for "EH"
+ * - one for "B"
+ * - one for "ER"
+ * - one for "W"
+ *
+ * Again, we are painting the third TextRenderedRun. The left clip edge
+ * is the advance of the "W" and the right clip edge is the sum of the
+ * advances of "BEH". Our translation to get the rendered "ER" glyphs
+ * in the right place this time is:
+ *
+ * (-frameWidth + rightEdge, -baseline)
+ *
+ * which is equivalent to:
+ *
+ * (-(leftEdge + advance("ER")), -baseline)
+ *
+ * The reason we have to shift left additionally by the width of the run
+ * of glyphs we are painting is that although the nsTextFrame is RTL,
+ * we still supply the top-left corner to paint the frame at when calling
+ * nsTextFrame::PaintText, even though our user space positions for each
+ * glyph in mPositions specifies the origin of each glyph, which for RTL
+ * glyphs is at the right edge of the glyph cell.
+ *
+ *
+ * For any other use of an nsTextFrame in the context of a particular run
+ * (such as hit testing, or getting its rectangle),
+ * GetTransformFromRunUserSpaceToUserSpace should be used.
+ *
+ * @param aContext The context to use for unit conversions.
+ */
+ gfxMatrix GetTransformFromUserSpaceForPainting(
+ nsPresContext* aContext, const nscoord aVisIStartEdge,
+ const nscoord aVisIEndEdge) const;
+
+ /**
+ * Returns the transform that converts from "run user space" to a <text>
+ * element's user space. Run user space is a coordinate system that has the
+ * same size as the <text>'s user space but rotated and translated such that
+ * (0,0) is the top-left of the rectangle that bounds the text.
+ *
+ * @param aContext The context to use for unit conversions.
+ */
+ gfxMatrix GetTransformFromRunUserSpaceToUserSpace(
+ nsPresContext* aContext) const;
+
+ /**
+ * Returns the transform that converts from "run user space" to float pixels
+ * relative to the nsTextFrame that this rendered run is a part of.
+ *
+ * @param aContext The context to use for unit conversions.
+ */
+ gfxMatrix GetTransformFromRunUserSpaceToFrameUserSpace(
+ nsPresContext* aContext) const;
+
+ /**
+ * Flag values used for the aFlags arguments of GetRunUserSpaceRect,
+ * GetFrameUserSpaceRect and GetUserSpaceRect.
+ */
+ enum {
+ // Includes the fill geometry of the text in the returned rectangle.
+ eIncludeFill = 1,
+ // Includes the stroke geometry of the text in the returned rectangle.
+ eIncludeStroke = 2,
+ // Includes any text shadow in the returned rectangle.
+ eIncludeTextShadow = 4,
+ // Don't include any horizontal glyph overflow in the returned rectangle.
+ eNoHorizontalOverflow = 8
+ };
+
+ /**
+ * Returns a rectangle that bounds the fill and/or stroke of the rendered run
+ * in run user space.
+ *
+ * @param aContext The context to use for unit conversions.
+ * @param aFlags A combination of the flags above (eIncludeFill and
+ * eIncludeStroke) indicating what parts of the text to include in
+ * the rectangle.
+ */
+ SVGBBox GetRunUserSpaceRect(nsPresContext* aContext, uint32_t aFlags) const;
+
+ /**
+ * Returns a rectangle that covers the fill and/or stroke of the rendered run
+ * in "frame user space".
+ *
+ * Frame user space is a coordinate space of the same scale as the <text>
+ * element's user space, but with its rotation set to the rotation of
+ * the glyphs within this rendered run and its origin set to the position
+ * such that placing the nsTextFrame there would result in the glyphs in
+ * this rendered run being at their correct positions.
+ *
+ * For example, say we have <text x="100 150" y="100">ab</text>. Assume
+ * the advance of both the "a" and the "b" is 12 user units, and the
+ * ascent of the text is 8 user units and its descent is 6 user units,
+ * and that we are not measuing the stroke of the text, so that we stay
+ * entirely within the glyph cells.
+ *
+ * There will be two text rendered runs, one for "a" and one for "b".
+ *
+ * The frame user space for the "a" run will have its origin at
+ * (100, 100 - 8) in the <text> element's user space and will have its
+ * axes aligned with the user space (since there is no rotate="" or
+ * text path involve) and with its scale the same as the user space.
+ * The rect returned by this method will be (0, 0, 12, 14), since the "a"
+ * glyph is right at the left of the nsTextFrame.
+ *
+ * The frame user space for the "b" run will have its origin at
+ * (150 - 12, 100 - 8), and scale/rotation the same as above. The rect
+ * returned by this method will be (12, 0, 12, 14), since we are
+ * advance("a") horizontally in to the text frame.
+ *
+ * @param aContext The context to use for unit conversions.
+ * @param aFlags A combination of the flags above (eIncludeFill and
+ * eIncludeStroke) indicating what parts of the text to include in
+ * the rectangle.
+ */
+ SVGBBox GetFrameUserSpaceRect(nsPresContext* aContext, uint32_t aFlags) const;
+
+ /**
+ * Returns a rectangle that covers the fill and/or stroke of the rendered run
+ * in the <text> element's user space.
+ *
+ * @param aContext The context to use for unit conversions.
+ * @param aFlags A combination of the flags above indicating what parts of
+ * the text to include in the rectangle.
+ * @param aAdditionalTransform An additional transform to apply to the
+ * frame user space rectangle before its bounds are transformed into
+ * user space.
+ */
+ SVGBBox GetUserSpaceRect(
+ nsPresContext* aContext, uint32_t aFlags,
+ const gfxMatrix* aAdditionalTransform = nullptr) const;
+
+ /**
+ * Gets the app unit amounts to clip from the left and right edges of
+ * the nsTextFrame in order to paint just this rendered run.
+ *
+ * Note that if clip edge amounts land in the middle of a glyph, the
+ * glyph won't be painted at all. The clip edges are thus more of
+ * a selection mechanism for which glyphs will be painted, rather
+ * than a geometric clip.
+ */
+ void GetClipEdges(nscoord& aVisIStartEdge, nscoord& aVisIEndEdge) const;
+
+ /**
+ * Returns the advance width of the whole rendered run.
+ */
+ nscoord GetAdvanceWidth() const;
+
+ /**
+ * Returns the index of the character into this rendered run whose
+ * glyph cell contains the given point, or -1 if there is no such
+ * character. This does not hit test against any overflow.
+ *
+ * @param aContext The context to use for unit conversions.
+ * @param aPoint The point in the user space of the <text> element.
+ */
+ int32_t GetCharNumAtPosition(nsPresContext* aContext,
+ const gfxPoint& aPoint) const;
+
+ /**
+ * The text frame that this rendered run lies within.
+ */
+ nsTextFrame* mFrame;
+
+ /**
+ * The point in user space that the text is positioned at.
+ *
+ * For a horizontal run:
+ * The x coordinate is the left edge of a LTR run of text or the right edge of
+ * an RTL run. The y coordinate is the baseline of the text.
+ * For a vertical run:
+ * The x coordinate is the baseline of the text.
+ * The y coordinate is the top edge of a LTR run, or bottom of RTL.
+ */
+ gfxPoint mPosition;
+
+ /**
+ * The horizontal scale factor to apply when painting glyphs to take
+ * into account textLength="".
+ */
+ float mLengthAdjustScaleFactor;
+
+ /**
+ * The rotation in radians in the user coordinate system that the text has.
+ */
+ float mRotate;
+
+ /**
+ * The scale factor that was used to transform the text run's original font
+ * size into a sane range for painting and measurement.
+ */
+ double mFontSizeScaleFactor;
+
+ /**
+ * The baseline in app units of this text run. The measurement is from the
+ * top of the text frame. (From the left edge if vertical.)
+ */
+ nscoord mBaseline;
+
+ /**
+ * The offset and length in mFrame's content Text that corresponds to
+ * this text rendered run. These are original char indexes.
+ */
+ uint32_t mTextFrameContentOffset;
+ uint32_t mTextFrameContentLength;
+
+ /**
+ * The character index in the whole SVG <text> element that this text rendered
+ * run begins at.
+ */
+ uint32_t mTextElementCharIndex;
+};
+
+gfxMatrix TextRenderedRun::GetTransformFromUserSpaceForPainting(
+ nsPresContext* aContext, const nscoord aVisIStartEdge,
+ const nscoord aVisIEndEdge) const {
+ // We transform to device pixels positioned such that painting the text frame
+ // at (0,0) with aItem will result in the text being in the right place.
+
+ gfxMatrix m;
+ if (!mFrame) {
+ return m;
+ }
+
+ float cssPxPerDevPx =
+ nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
+
+ // Glyph position in user space.
+ m.PreTranslate(mPosition / cssPxPerDevPx);
+
+ // Take into account any font size scaling and scaling due to textLength="".
+ m.PreScale(1.0 / mFontSizeScaleFactor, 1.0 / mFontSizeScaleFactor);
+
+ // Rotation due to rotate="" or a <textPath>.
+ m.PreRotate(mRotate);
+
+ m.PreScale(mLengthAdjustScaleFactor, 1.0);
+
+ // Translation to get the text frame in the right place.
+ nsPoint t;
+
+ if (IsVertical()) {
+ t = nsPoint(-mBaseline, IsRightToLeft()
+ ? -mFrame->GetRect().height + aVisIEndEdge
+ : -aVisIStartEdge);
+ } else {
+ t = nsPoint(IsRightToLeft() ? -mFrame->GetRect().width + aVisIEndEdge
+ : -aVisIStartEdge,
+ -mBaseline);
+ }
+ m.PreTranslate(AppUnitsToGfxUnits(t, aContext));
+
+ return m;
+}
+
+gfxMatrix TextRenderedRun::GetTransformFromRunUserSpaceToUserSpace(
+ nsPresContext* aContext) const {
+ gfxMatrix m;
+ if (!mFrame) {
+ return m;
+ }
+
+ float cssPxPerDevPx =
+ nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
+
+ nscoord start, end;
+ GetClipEdges(start, end);
+
+ // Glyph position in user space.
+ m.PreTranslate(mPosition);
+
+ // Rotation due to rotate="" or a <textPath>.
+ m.PreRotate(mRotate);
+
+ // Scale due to textLength="".
+ m.PreScale(mLengthAdjustScaleFactor, 1.0);
+
+ // Translation to get the text frame in the right place.
+ nsPoint t;
+ if (IsVertical()) {
+ t = nsPoint(-mBaseline,
+ IsRightToLeft() ? -mFrame->GetRect().height + start + end : 0);
+ } else {
+ t = nsPoint(IsRightToLeft() ? -mFrame->GetRect().width + start + end : 0,
+ -mBaseline);
+ }
+ m.PreTranslate(AppUnitsToGfxUnits(t, aContext) * cssPxPerDevPx /
+ mFontSizeScaleFactor);
+
+ return m;
+}
+
+gfxMatrix TextRenderedRun::GetTransformFromRunUserSpaceToFrameUserSpace(
+ nsPresContext* aContext) const {
+ gfxMatrix m;
+ if (!mFrame) {
+ return m;
+ }
+
+ nscoord start, end;
+ GetClipEdges(start, end);
+
+ // Translate by the horizontal distance into the text frame this
+ // rendered run is.
+ gfxFloat appPerCssPx = AppUnitsPerCSSPixel();
+ gfxPoint t = IsVertical() ? gfxPoint(0, start / appPerCssPx)
+ : gfxPoint(start / appPerCssPx, 0);
+ return m.PreTranslate(t);
+}
+
+SVGBBox TextRenderedRun::GetRunUserSpaceRect(nsPresContext* aContext,
+ uint32_t aFlags) const {
+ SVGBBox r;
+ if (!mFrame) {
+ return r;
+ }
+
+ // Determine the amount of overflow above and below the frame's mRect.
+ //
+ // We need to call InkOverflowRectRelativeToSelf because this includes
+ // overflowing decorations, which the MeasureText call below does not. We
+ // assume here the decorations only overflow above and below the frame, never
+ // horizontally.
+ nsRect self = mFrame->InkOverflowRectRelativeToSelf();
+ nsRect rect = mFrame->GetRect();
+ bool vertical = IsVertical();
+ nscoord above = vertical ? -self.x : -self.y;
+ nscoord below =
+ vertical ? self.XMost() - rect.width : self.YMost() - rect.height;
+
+ gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
+ gfxSkipCharsIterator start = it;
+ gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
+
+ // Get the content range for this rendered run.
+ Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
+ mTextFrameContentLength);
+ if (range.Length() == 0) {
+ return r;
+ }
+
+ // FIXME(heycam): We could create a single PropertyProvider for all
+ // TextRenderedRuns that correspond to the text frame, rather than recreate
+ // it each time here.
+ nsTextFrame::PropertyProvider provider(mFrame, start);
+
+ // Measure that range.
+ gfxTextRun::Metrics metrics = textRun->MeasureText(
+ range, gfxFont::LOOSE_INK_EXTENTS, nullptr, &provider);
+ // Make sure it includes the font-box.
+ gfxRect fontBox(0, -metrics.mAscent, metrics.mAdvanceWidth,
+ metrics.mAscent + metrics.mDescent);
+ metrics.mBoundingBox.UnionRect(metrics.mBoundingBox, fontBox);
+
+ // Determine the rectangle that covers the rendered run's fill,
+ // taking into account the measured vertical overflow due to
+ // decorations.
+ nscoord baseline =
+ NSToCoordRoundWithClamp(metrics.mBoundingBox.y + metrics.mAscent);
+ gfxFloat x, width;
+ if (aFlags & eNoHorizontalOverflow) {
+ x = 0.0;
+ width = textRun->GetAdvanceWidth(range, &provider);
+ if (width < 0.0) {
+ x = width;
+ width = -width;
+ }
+ } else {
+ x = metrics.mBoundingBox.x;
+ width = metrics.mBoundingBox.width;
+ }
+ nsRect fillInAppUnits(
+ NSToCoordRoundWithClamp(x), baseline - above,
+ NSToCoordRoundWithClamp(width),
+ NSToCoordRoundWithClamp(metrics.mBoundingBox.height) + above + below);
+ if (textRun->IsVertical()) {
+ // Swap line-relative textMetrics dimensions to physical coordinates.
+ std::swap(fillInAppUnits.x, fillInAppUnits.y);
+ std::swap(fillInAppUnits.width, fillInAppUnits.height);
+ }
+
+ // Account for text-shadow.
+ if (aFlags & eIncludeTextShadow) {
+ fillInAppUnits =
+ nsLayoutUtils::GetTextShadowRectsUnion(fillInAppUnits, mFrame);
+ }
+
+ // Convert the app units rectangle to user units.
+ gfxRect fill = AppUnitsToFloatCSSPixels(
+ gfxRect(fillInAppUnits.x, fillInAppUnits.y, fillInAppUnits.width,
+ fillInAppUnits.height),
+ aContext);
+
+ // Scale the rectangle up due to any mFontSizeScaleFactor.
+ fill.Scale(1.0 / mFontSizeScaleFactor);
+
+ // Include the fill if requested.
+ if (aFlags & eIncludeFill) {
+ r = fill;
+ }
+
+ // Include the stroke if requested.
+ if ((aFlags & eIncludeStroke) && !fill.IsEmpty() &&
+ SVGUtils::GetStrokeWidth(mFrame) > 0) {
+ r.UnionEdges(
+ SVGUtils::PathExtentsToMaxStrokeExtents(fill, mFrame, gfxMatrix()));
+ }
+
+ return r;
+}
+
+SVGBBox TextRenderedRun::GetFrameUserSpaceRect(nsPresContext* aContext,
+ uint32_t aFlags) const {
+ SVGBBox r = GetRunUserSpaceRect(aContext, aFlags);
+ if (r.IsEmpty()) {
+ return r;
+ }
+ gfxMatrix m = GetTransformFromRunUserSpaceToFrameUserSpace(aContext);
+ return m.TransformBounds(r.ToThebesRect());
+}
+
+SVGBBox TextRenderedRun::GetUserSpaceRect(
+ nsPresContext* aContext, uint32_t aFlags,
+ const gfxMatrix* aAdditionalTransform) const {
+ SVGBBox r = GetRunUserSpaceRect(aContext, aFlags);
+ if (r.IsEmpty()) {
+ return r;
+ }
+ gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext);
+ if (aAdditionalTransform) {
+ m *= *aAdditionalTransform;
+ }
+ return m.TransformBounds(r.ToThebesRect());
+}
+
+void TextRenderedRun::GetClipEdges(nscoord& aVisIStartEdge,
+ nscoord& aVisIEndEdge) const {
+ uint32_t contentLength = mFrame->GetContentLength();
+ if (mTextFrameContentOffset == 0 &&
+ mTextFrameContentLength == contentLength) {
+ // If the rendered run covers the entire content, we know we don't need
+ // to clip without having to measure anything.
+ aVisIStartEdge = 0;
+ aVisIEndEdge = 0;
+ return;
+ }
+
+ gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
+ nsTextFrame::PropertyProvider provider(mFrame, it);
+
+ // Get the covered content offset/length for this rendered run in skipped
+ // characters, since that is what GetAdvanceWidth expects.
+ Range runRange = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
+ mTextFrameContentLength);
+
+ // Get the offset/length of the whole nsTextFrame.
+ uint32_t frameOffset = mFrame->GetContentOffset();
+ uint32_t frameLength = mFrame->GetContentLength();
+
+ // Trim the whole-nsTextFrame offset/length to remove any leading/trailing
+ // white space, as the nsTextFrame when painting does not include them when
+ // interpreting clip edges.
+ nsTextFrame::TrimmedOffsets trimmedOffsets =
+ mFrame->GetTrimmedOffsets(mFrame->TextFragment());
+ TrimOffsets(frameOffset, frameLength, trimmedOffsets);
+
+ // Convert the trimmed whole-nsTextFrame offset/length into skipped
+ // characters.
+ Range frameRange = ConvertOriginalToSkipped(it, frameOffset, frameLength);
+
+ // Measure the advance width in the text run between the start of
+ // frame's content and the start of the rendered run's content,
+ nscoord startEdge = textRun->GetAdvanceWidth(
+ Range(frameRange.start, runRange.start), &provider);
+
+ // and between the end of the rendered run's content and the end
+ // of the frame's content.
+ nscoord endEdge =
+ textRun->GetAdvanceWidth(Range(runRange.end, frameRange.end), &provider);
+
+ if (textRun->IsRightToLeft()) {
+ aVisIStartEdge = endEdge;
+ aVisIEndEdge = startEdge;
+ } else {
+ aVisIStartEdge = startEdge;
+ aVisIEndEdge = endEdge;
+ }
+}
+
+nscoord TextRenderedRun::GetAdvanceWidth() const {
+ gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
+ nsTextFrame::PropertyProvider provider(mFrame, it);
+
+ Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
+ mTextFrameContentLength);
+
+ return textRun->GetAdvanceWidth(range, &provider);
+}
+
+int32_t TextRenderedRun::GetCharNumAtPosition(nsPresContext* aContext,
+ const gfxPoint& aPoint) const {
+ if (mTextFrameContentLength == 0) {
+ return -1;
+ }
+
+ float cssPxPerDevPx =
+ nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
+
+ // Convert the point from user space into run user space, and take
+ // into account any mFontSizeScaleFactor.
+ gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext);
+ if (!m.Invert()) {
+ return -1;
+ }
+ gfxPoint p = m.TransformPoint(aPoint) / cssPxPerDevPx * mFontSizeScaleFactor;
+
+ // First check that the point lies vertically between the top and bottom
+ // edges of the text.
+ gfxFloat ascent, descent;
+ GetAscentAndDescentInAppUnits(mFrame, ascent, descent);
+
+ WritingMode writingMode = mFrame->GetWritingMode();
+ if (writingMode.IsVertical()) {
+ gfxFloat leftEdge = mFrame->GetLogicalBaseline(writingMode) -
+ (writingMode.IsVerticalRL() ? ascent : descent);
+ gfxFloat rightEdge = leftEdge + ascent + descent;
+ if (p.x < aContext->AppUnitsToGfxUnits(leftEdge) ||
+ p.x > aContext->AppUnitsToGfxUnits(rightEdge)) {
+ return -1;
+ }
+ } else {
+ gfxFloat topEdge = mFrame->GetLogicalBaseline(writingMode) - ascent;
+ gfxFloat bottomEdge = topEdge + ascent + descent;
+ if (p.y < aContext->AppUnitsToGfxUnits(topEdge) ||
+ p.y > aContext->AppUnitsToGfxUnits(bottomEdge)) {
+ return -1;
+ }
+ }
+
+ gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
+ nsTextFrame::PropertyProvider provider(mFrame, it);
+
+ // Next check that the point lies horizontally within the left and right
+ // edges of the text.
+ Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
+ mTextFrameContentLength);
+ gfxFloat runAdvance =
+ aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(range, &provider));
+
+ gfxFloat pos = writingMode.IsVertical() ? p.y : p.x;
+ if (pos < 0 || pos >= runAdvance) {
+ return -1;
+ }
+
+ // Finally, measure progressively smaller portions of the rendered run to
+ // find which glyph it lies within. This will need to change once we
+ // support letter-spacing and word-spacing.
+ bool rtl = textRun->IsRightToLeft();
+ for (int32_t i = mTextFrameContentLength - 1; i >= 0; i--) {
+ range = ConvertOriginalToSkipped(it, mTextFrameContentOffset, i);
+ gfxFloat advance = aContext->AppUnitsToGfxUnits(
+ textRun->GetAdvanceWidth(range, &provider));
+ if ((rtl && pos < runAdvance - advance) || (!rtl && pos >= advance)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+// ----------------------------------------------------------------------------
+// TextNodeIterator
+
+enum SubtreePosition { eBeforeSubtree, eWithinSubtree, eAfterSubtree };
+
+/**
+ * An iterator class for Text that are descendants of a given node, the
+ * root. Nodes are iterated in document order. An optional subtree can be
+ * specified, in which case the iterator will track whether the current state of
+ * the traversal over the tree is within that subtree or is past that subtree.
+ */
+class TextNodeIterator {
+ public:
+ /**
+ * Constructs a TextNodeIterator with the specified root node and optional
+ * subtree.
+ */
+ explicit TextNodeIterator(nsIContent* aRoot, nsIContent* aSubtree = nullptr)
+ : mRoot(aRoot),
+ mSubtree(aSubtree == aRoot ? nullptr : aSubtree),
+ mCurrent(aRoot),
+ mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) {
+ NS_ASSERTION(aRoot, "expected non-null root");
+ if (!aRoot->IsText()) {
+ Next();
+ }
+ }
+
+ /**
+ * Returns the current Text, or null if the iterator has finished.
+ */
+ Text* Current() const { return mCurrent ? mCurrent->AsText() : nullptr; }
+
+ /**
+ * Advances to the next Text and returns it, or null if the end of
+ * iteration has been reached.
+ */
+ Text* Next();
+
+ /**
+ * Returns whether the iterator is currently within the subtree rooted
+ * at mSubtree. Returns true if we are not tracking a subtree (we consider
+ * that we're always within the subtree).
+ */
+ bool IsWithinSubtree() const { return mSubtreePosition == eWithinSubtree; }
+
+ /**
+ * Returns whether the iterator is past the subtree rooted at mSubtree.
+ * Returns false if we are not tracking a subtree.
+ */
+ bool IsAfterSubtree() const { return mSubtreePosition == eAfterSubtree; }
+
+ private:
+ /**
+ * The root under which all Text will be iterated over.
+ */
+ nsIContent* const mRoot;
+
+ /**
+ * The node rooting the subtree to track.
+ */
+ nsIContent* const mSubtree;
+
+ /**
+ * The current node during iteration.
+ */
+ nsIContent* mCurrent;
+
+ /**
+ * The current iterator position relative to mSubtree.
+ */
+ SubtreePosition mSubtreePosition;
+};
+
+Text* TextNodeIterator::Next() {
+ // Starting from mCurrent, we do a non-recursive traversal to the next
+ // Text beneath mRoot, updating mSubtreePosition appropriately if we
+ // encounter mSubtree.
+ if (mCurrent) {
+ do {
+ nsIContent* next =
+ IsTextContentElement(mCurrent) ? mCurrent->GetFirstChild() : nullptr;
+ if (next) {
+ mCurrent = next;
+ if (mCurrent == mSubtree) {
+ mSubtreePosition = eWithinSubtree;
+ }
+ } else {
+ for (;;) {
+ if (mCurrent == mRoot) {
+ mCurrent = nullptr;
+ break;
+ }
+ if (mCurrent == mSubtree) {
+ mSubtreePosition = eAfterSubtree;
+ }
+ next = mCurrent->GetNextSibling();
+ if (next) {
+ mCurrent = next;
+ if (mCurrent == mSubtree) {
+ mSubtreePosition = eWithinSubtree;
+ }
+ break;
+ }
+ if (mCurrent == mSubtree) {
+ mSubtreePosition = eAfterSubtree;
+ }
+ mCurrent = mCurrent->GetParent();
+ }
+ }
+ } while (mCurrent && !mCurrent->IsText());
+ }
+
+ return mCurrent ? mCurrent->AsText() : nullptr;
+}
+
+// ----------------------------------------------------------------------------
+// TextNodeCorrespondenceRecorder
+
+/**
+ * TextNodeCorrespondence is used as the value of a frame property that
+ * is stored on all its descendant nsTextFrames. It stores the number of DOM
+ * characters between it and the previous nsTextFrame that did not have an
+ * nsTextFrame created for them, due to either not being in a correctly
+ * parented text content element, or because they were display:none.
+ * These are called "undisplayed characters".
+ *
+ * See also TextNodeCorrespondenceRecorder below, which is what sets the
+ * frame property.
+ */
+struct TextNodeCorrespondence {
+ explicit TextNodeCorrespondence(uint32_t aUndisplayedCharacters)
+ : mUndisplayedCharacters(aUndisplayedCharacters) {}
+
+ uint32_t mUndisplayedCharacters;
+};
+
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(TextNodeCorrespondenceProperty,
+ TextNodeCorrespondence)
+
+/**
+ * Returns the number of undisplayed characters before the specified
+ * nsTextFrame.
+ */
+static uint32_t GetUndisplayedCharactersBeforeFrame(nsTextFrame* aFrame) {
+ void* value = aFrame->GetProperty(TextNodeCorrespondenceProperty());
+ TextNodeCorrespondence* correspondence =
+ static_cast<TextNodeCorrespondence*>(value);
+ if (!correspondence) {
+ // FIXME bug 903785
+ NS_ERROR(
+ "expected a TextNodeCorrespondenceProperty on nsTextFrame "
+ "used for SVG text");
+ return 0;
+ }
+ return correspondence->mUndisplayedCharacters;
+}
+
+/**
+ * Traverses the nsTextFrames for an SVGTextFrame and records a
+ * TextNodeCorrespondenceProperty on each for the number of undisplayed DOM
+ * characters between each frame. This is done by iterating simultaneously
+ * over the Text and nsTextFrames and noting when Text (or
+ * parts of them) are skipped when finding the next nsTextFrame.
+ */
+class TextNodeCorrespondenceRecorder {
+ public:
+ /**
+ * Entry point for the TextNodeCorrespondenceProperty recording.
+ */
+ static void RecordCorrespondence(SVGTextFrame* aRoot);
+
+ private:
+ explicit TextNodeCorrespondenceRecorder(SVGTextFrame* aRoot)
+ : mNodeIterator(aRoot->GetContent()),
+ mPreviousNode(nullptr),
+ mNodeCharIndex(0) {}
+
+ void Record(SVGTextFrame* aRoot);
+ void TraverseAndRecord(nsIFrame* aFrame);
+
+ /**
+ * Returns the next non-empty Text.
+ */
+ Text* NextNode();
+
+ /**
+ * The iterator over the Text that we use as we simultaneously
+ * iterate over the nsTextFrames.
+ */
+ TextNodeIterator mNodeIterator;
+
+ /**
+ * The previous Text we iterated over.
+ */
+ Text* mPreviousNode;
+
+ /**
+ * The index into the current Text's character content.
+ */
+ uint32_t mNodeCharIndex;
+};
+
+/* static */
+void TextNodeCorrespondenceRecorder::RecordCorrespondence(SVGTextFrame* aRoot) {
+ if (aRoot->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY)) {
+ // Resolve bidi so that continuation frames are created if necessary:
+ aRoot->MaybeResolveBidiForAnonymousBlockChild();
+ TextNodeCorrespondenceRecorder recorder(aRoot);
+ recorder.Record(aRoot);
+ aRoot->RemoveStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY);
+ }
+}
+
+void TextNodeCorrespondenceRecorder::Record(SVGTextFrame* aRoot) {
+ if (!mNodeIterator.Current()) {
+ // If there are no Text nodes then there is nothing to do.
+ return;
+ }
+
+ // Traverse over all the nsTextFrames and record the number of undisplayed
+ // characters.
+ TraverseAndRecord(aRoot);
+
+ // Find how many undisplayed characters there are after the final nsTextFrame.
+ uint32_t undisplayed = 0;
+ if (mNodeIterator.Current()) {
+ if (mPreviousNode && mPreviousNode->TextLength() != mNodeCharIndex) {
+ // The last nsTextFrame ended part way through a Text node. The
+ // remaining characters count as undisplayed.
+ NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(),
+ "incorrect tracking of undisplayed characters in "
+ "text nodes");
+ undisplayed += mPreviousNode->TextLength() - mNodeCharIndex;
+ }
+ // All the remaining Text that we iterate must also be undisplayed.
+ for (Text* textNode = mNodeIterator.Current(); textNode;
+ textNode = NextNode()) {
+ undisplayed += textNode->TextLength();
+ }
+ }
+
+ // Record the trailing number of undisplayed characters on the
+ // SVGTextFrame.
+ aRoot->mTrailingUndisplayedCharacters = undisplayed;
+}
+
+Text* TextNodeCorrespondenceRecorder::NextNode() {
+ mPreviousNode = mNodeIterator.Current();
+ Text* next;
+ do {
+ next = mNodeIterator.Next();
+ } while (next && next->TextLength() == 0);
+ return next;
+}
+
+void TextNodeCorrespondenceRecorder::TraverseAndRecord(nsIFrame* aFrame) {
+ // Recursively iterate over the frame tree, for frames that correspond
+ // to text content elements.
+ if (IsTextContentElement(aFrame->GetContent())) {
+ for (nsIFrame* f : aFrame->PrincipalChildList()) {
+ TraverseAndRecord(f);
+ }
+ return;
+ }
+
+ nsTextFrame* frame; // The current text frame.
+ Text* node; // The text node for the current text frame.
+ if (!GetNonEmptyTextFrameAndNode(aFrame, frame, node)) {
+ // If this isn't an nsTextFrame, or is empty, nothing to do.
+ return;
+ }
+
+ NS_ASSERTION(frame->GetContentOffset() >= 0,
+ "don't know how to handle negative content indexes");
+
+ uint32_t undisplayed = 0;
+ if (!mPreviousNode) {
+ // Must be the very first text frame.
+ NS_ASSERTION(mNodeCharIndex == 0,
+ "incorrect tracking of undisplayed "
+ "characters in text nodes");
+ if (!mNodeIterator.Current()) {
+ MOZ_ASSERT_UNREACHABLE(
+ "incorrect tracking of correspondence between "
+ "text frames and text nodes");
+ } else {
+ // Each whole Text we find before we get to the text node for the
+ // first text frame must be undisplayed.
+ while (mNodeIterator.Current() != node) {
+ undisplayed += mNodeIterator.Current()->TextLength();
+ NextNode();
+ }
+ // If the first text frame starts at a non-zero content offset, then those
+ // earlier characters are also undisplayed.
+ undisplayed += frame->GetContentOffset();
+ NextNode();
+ }
+ } else if (mPreviousNode == node) {
+ // Same text node as last time.
+ if (static_cast<uint32_t>(frame->GetContentOffset()) != mNodeCharIndex) {
+ // We have some characters in the middle of the text node
+ // that are undisplayed.
+ NS_ASSERTION(
+ mNodeCharIndex < static_cast<uint32_t>(frame->GetContentOffset()),
+ "incorrect tracking of undisplayed characters in "
+ "text nodes");
+ undisplayed = frame->GetContentOffset() - mNodeCharIndex;
+ }
+ } else {
+ // Different text node from last time.
+ if (mPreviousNode->TextLength() != mNodeCharIndex) {
+ NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(),
+ "incorrect tracking of undisplayed characters in "
+ "text nodes");
+ // Any trailing characters at the end of the previous Text are
+ // undisplayed.
+ undisplayed = mPreviousNode->TextLength() - mNodeCharIndex;
+ }
+ // Each whole Text we find before we get to the text node for
+ // the current text frame must be undisplayed.
+ while (mNodeIterator.Current() && mNodeIterator.Current() != node) {
+ undisplayed += mNodeIterator.Current()->TextLength();
+ NextNode();
+ }
+ // If the current text frame starts at a non-zero content offset, then those
+ // earlier characters are also undisplayed.
+ undisplayed += frame->GetContentOffset();
+ NextNode();
+ }
+
+ // Set the frame property.
+ frame->SetProperty(TextNodeCorrespondenceProperty(),
+ new TextNodeCorrespondence(undisplayed));
+
+ // Remember how far into the current Text we are.
+ mNodeCharIndex = frame->GetContentEnd();
+}
+
+// ----------------------------------------------------------------------------
+// TextFrameIterator
+
+/**
+ * An iterator class for nsTextFrames that are descendants of an
+ * SVGTextFrame. The iterator can optionally track whether the
+ * current nsTextFrame is for a descendant of, or past, a given subtree
+ * content node or frame. (This functionality is used for example by the SVG
+ * DOM text methods to get only the nsTextFrames for a particular <tspan>.)
+ *
+ * TextFrameIterator also tracks and exposes other information about the
+ * current nsTextFrame:
+ *
+ * * how many undisplayed characters came just before it
+ * * its position (in app units) relative to the SVGTextFrame's anonymous
+ * block frame
+ * * what nsInlineFrame corresponding to a <textPath> element it is a
+ * descendant of
+ * * what computed dominant-baseline value applies to it
+ *
+ * Note that any text frames that are empty -- whose ContentLength() is 0 --
+ * will be skipped over.
+ */
+class MOZ_STACK_CLASS TextFrameIterator {
+ public:
+ /**
+ * Constructs a TextFrameIterator for the specified SVGTextFrame
+ * with an optional frame subtree to restrict iterated text frames to.
+ */
+ explicit TextFrameIterator(SVGTextFrame* aRoot,
+ const nsIFrame* aSubtree = nullptr)
+ : mRootFrame(aRoot),
+ mSubtree(aSubtree),
+ mCurrentFrame(aRoot),
+ mCurrentPosition(),
+ mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) {
+ Init();
+ }
+
+ /**
+ * Constructs a TextFrameIterator for the specified SVGTextFrame
+ * with an optional frame content subtree to restrict iterated text frames to.
+ */
+ TextFrameIterator(SVGTextFrame* aRoot, nsIContent* aSubtree)
+ : mRootFrame(aRoot),
+ mSubtree(aRoot && aSubtree && aSubtree != aRoot->GetContent()
+ ? aSubtree->GetPrimaryFrame()
+ : nullptr),
+ mCurrentFrame(aRoot),
+ mCurrentPosition(),
+ mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) {
+ Init();
+ }
+
+ /**
+ * Returns the root SVGTextFrame this TextFrameIterator is iterating over.
+ */
+ SVGTextFrame* Root() const { return mRootFrame; }
+
+ /**
+ * Returns the current nsTextFrame.
+ */
+ nsTextFrame* Current() const { return do_QueryFrame(mCurrentFrame); }
+
+ /**
+ * Returns the number of undisplayed characters in the DOM just before the
+ * current frame.
+ */
+ uint32_t UndisplayedCharacters() const;
+
+ /**
+ * Returns the current frame's position, in app units, relative to the
+ * root SVGTextFrame's anonymous block frame.
+ */
+ nsPoint Position() const { return mCurrentPosition; }
+
+ /**
+ * Advances to the next nsTextFrame and returns it.
+ */
+ nsTextFrame* Next();
+
+ /**
+ * Returns whether the iterator is within the subtree.
+ */
+ bool IsWithinSubtree() const { return mSubtreePosition == eWithinSubtree; }
+
+ /**
+ * Returns whether the iterator is past the subtree.
+ */
+ bool IsAfterSubtree() const { return mSubtreePosition == eAfterSubtree; }
+
+ /**
+ * Returns the frame corresponding to the <textPath> element, if we
+ * are inside one.
+ */
+ nsIFrame* TextPathFrame() const {
+ return mTextPathFrames.IsEmpty()
+ ? nullptr
+ : mTextPathFrames.ElementAt(mTextPathFrames.Length() - 1);
+ }
+
+ /**
+ * Returns the current frame's computed dominant-baseline value.
+ */
+ StyleDominantBaseline DominantBaseline() const {
+ return mBaselines.ElementAt(mBaselines.Length() - 1);
+ }
+
+ /**
+ * Finishes the iterator.
+ */
+ void Close() { mCurrentFrame = nullptr; }
+
+ private:
+ /**
+ * Initializes the iterator and advances to the first item.
+ */
+ void Init() {
+ if (!mRootFrame) {
+ return;
+ }
+
+ mBaselines.AppendElement(mRootFrame->StyleSVG()->mDominantBaseline);
+ Next();
+ }
+
+ /**
+ * Pushes the specified frame's computed dominant-baseline value.
+ * If the value of the property is "auto", then the parent frame's
+ * computed value is used.
+ */
+ void PushBaseline(nsIFrame* aNextFrame);
+
+ /**
+ * Pops the current dominant-baseline off the stack.
+ */
+ void PopBaseline();
+
+ /**
+ * The root frame we are iterating through.
+ */
+ SVGTextFrame* const mRootFrame;
+
+ /**
+ * The frame for the subtree we are also interested in tracking.
+ */
+ const nsIFrame* const mSubtree;
+
+ /**
+ * The current value of the iterator.
+ */
+ nsIFrame* mCurrentFrame;
+
+ /**
+ * The position, in app units, of the current frame relative to mRootFrame.
+ */
+ nsPoint mCurrentPosition;
+
+ /**
+ * Stack of frames corresponding to <textPath> elements that are in scope
+ * for the current frame.
+ */
+ AutoTArray<nsIFrame*, 1> mTextPathFrames;
+
+ /**
+ * Stack of dominant-baseline values to record as we traverse through the
+ * frame tree.
+ */
+ AutoTArray<StyleDominantBaseline, 8> mBaselines;
+
+ /**
+ * The iterator's current position relative to mSubtree.
+ */
+ SubtreePosition mSubtreePosition;
+};
+
+uint32_t TextFrameIterator::UndisplayedCharacters() const {
+ MOZ_ASSERT(
+ !mRootFrame->HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY),
+ "Text correspondence must be up to date");
+
+ if (!mCurrentFrame) {
+ return mRootFrame->mTrailingUndisplayedCharacters;
+ }
+
+ nsTextFrame* frame = do_QueryFrame(mCurrentFrame);
+ return GetUndisplayedCharactersBeforeFrame(frame);
+}
+
+nsTextFrame* TextFrameIterator::Next() {
+ // Starting from mCurrentFrame, we do a non-recursive traversal to the next
+ // nsTextFrame beneath mRoot, updating mSubtreePosition appropriately if we
+ // encounter mSubtree.
+ if (mCurrentFrame) {
+ do {
+ nsIFrame* next = IsTextContentElement(mCurrentFrame->GetContent())
+ ? mCurrentFrame->PrincipalChildList().FirstChild()
+ : nullptr;
+ if (next) {
+ // Descend into this frame, and accumulate its position.
+ mCurrentPosition += next->GetPosition();
+ if (next->GetContent()->IsSVGElement(nsGkAtoms::textPath)) {
+ // Record this <textPath> frame.
+ mTextPathFrames.AppendElement(next);
+ }
+ // Record the frame's baseline.
+ PushBaseline(next);
+ mCurrentFrame = next;
+ if (mCurrentFrame == mSubtree) {
+ // If the current frame is mSubtree, we have now moved into it.
+ mSubtreePosition = eWithinSubtree;
+ }
+ } else {
+ for (;;) {
+ // We want to move past the current frame.
+ if (mCurrentFrame == mRootFrame) {
+ // If we've reached the root frame, we're finished.
+ mCurrentFrame = nullptr;
+ break;
+ }
+ // Remove the current frame's position.
+ mCurrentPosition -= mCurrentFrame->GetPosition();
+ if (mCurrentFrame->GetContent()->IsSVGElement(nsGkAtoms::textPath)) {
+ // Pop off the <textPath> frame if this is a <textPath>.
+ mTextPathFrames.RemoveLastElement();
+ }
+ // Pop off the current baseline.
+ PopBaseline();
+ if (mCurrentFrame == mSubtree) {
+ // If this was mSubtree, we have now moved past it.
+ mSubtreePosition = eAfterSubtree;
+ }
+ next = mCurrentFrame->GetNextSibling();
+ if (next) {
+ // Moving to the next sibling.
+ mCurrentPosition += next->GetPosition();
+ if (next->GetContent()->IsSVGElement(nsGkAtoms::textPath)) {
+ // Record this <textPath> frame.
+ mTextPathFrames.AppendElement(next);
+ }
+ // Record the frame's baseline.
+ PushBaseline(next);
+ mCurrentFrame = next;
+ if (mCurrentFrame == mSubtree) {
+ // If the current frame is mSubtree, we have now moved into it.
+ mSubtreePosition = eWithinSubtree;
+ }
+ break;
+ }
+ if (mCurrentFrame == mSubtree) {
+ // If there is no next sibling frame, and the current frame is
+ // mSubtree, we have now moved past it.
+ mSubtreePosition = eAfterSubtree;
+ }
+ // Ascend out of this frame.
+ mCurrentFrame = mCurrentFrame->GetParent();
+ }
+ }
+ } while (mCurrentFrame && !IsNonEmptyTextFrame(mCurrentFrame));
+ }
+
+ return Current();
+}
+
+void TextFrameIterator::PushBaseline(nsIFrame* aNextFrame) {
+ StyleDominantBaseline baseline = aNextFrame->StyleSVG()->mDominantBaseline;
+ mBaselines.AppendElement(baseline);
+}
+
+void TextFrameIterator::PopBaseline() {
+ NS_ASSERTION(!mBaselines.IsEmpty(), "popped too many baselines");
+ mBaselines.RemoveLastElement();
+}
+
+// -----------------------------------------------------------------------------
+// TextRenderedRunIterator
+
+/**
+ * Iterator for TextRenderedRun objects for the SVGTextFrame.
+ */
+class TextRenderedRunIterator {
+ public:
+ /**
+ * Values for the aFilter argument of the constructor, to indicate which
+ * frames we should be limited to iterating TextRenderedRun objects for.
+ */
+ enum RenderedRunFilter {
+ // Iterate TextRenderedRuns for all nsTextFrames.
+ eAllFrames,
+ // Iterate only TextRenderedRuns for nsTextFrames that are
+ // visibility:visible.
+ eVisibleFrames
+ };
+
+ /**
+ * Constructs a TextRenderedRunIterator with an optional frame subtree to
+ * restrict iterated rendered runs to.
+ *
+ * @param aSVGTextFrame The SVGTextFrame whose rendered runs to iterate
+ * through.
+ * @param aFilter Indicates whether to iterate rendered runs for non-visible
+ * nsTextFrames.
+ * @param aSubtree An optional frame subtree to restrict iterated rendered
+ * runs to.
+ */
+ explicit TextRenderedRunIterator(SVGTextFrame* aSVGTextFrame,
+ RenderedRunFilter aFilter = eAllFrames,
+ const nsIFrame* aSubtree = nullptr)
+ : mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree),
+ mFilter(aFilter),
+ mTextElementCharIndex(0),
+ mFrameStartTextElementCharIndex(0),
+ mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor),
+ mCurrent(First()) {}
+
+ /**
+ * Constructs a TextRenderedRunIterator with a content subtree to restrict
+ * iterated rendered runs to.
+ *
+ * @param aSVGTextFrame The SVGTextFrame whose rendered runs to iterate
+ * through.
+ * @param aFilter Indicates whether to iterate rendered runs for non-visible
+ * nsTextFrames.
+ * @param aSubtree A content subtree to restrict iterated rendered runs to.
+ */
+ TextRenderedRunIterator(SVGTextFrame* aSVGTextFrame,
+ RenderedRunFilter aFilter, nsIContent* aSubtree)
+ : mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree),
+ mFilter(aFilter),
+ mTextElementCharIndex(0),
+ mFrameStartTextElementCharIndex(0),
+ mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor),
+ mCurrent(First()) {}
+
+ /**
+ * Returns the current TextRenderedRun.
+ */
+ TextRenderedRun Current() const { return mCurrent; }
+
+ /**
+ * Advances to the next TextRenderedRun and returns it.
+ */
+ TextRenderedRun Next();
+
+ private:
+ /**
+ * Returns the root SVGTextFrame this iterator is for.
+ */
+ SVGTextFrame* Root() const { return mFrameIterator.Root(); }
+
+ /**
+ * Advances to the first TextRenderedRun and returns it.
+ */
+ TextRenderedRun First();
+
+ /**
+ * The frame iterator to use.
+ */
+ TextFrameIterator mFrameIterator;
+
+ /**
+ * The filter indicating which TextRenderedRuns to return.
+ */
+ RenderedRunFilter mFilter;
+
+ /**
+ * The character index across the entire <text> element we are currently
+ * up to.
+ */
+ uint32_t mTextElementCharIndex;
+
+ /**
+ * The character index across the entire <text> for the start of the current
+ * frame.
+ */
+ uint32_t mFrameStartTextElementCharIndex;
+
+ /**
+ * The font-size scale factor we used when constructing the nsTextFrames.
+ */
+ double mFontSizeScaleFactor;
+
+ /**
+ * The current TextRenderedRun.
+ */
+ TextRenderedRun mCurrent;
+};
+
+TextRenderedRun TextRenderedRunIterator::Next() {
+ if (!mFrameIterator.Current()) {
+ // If there are no more frames, then there are no more rendered runs to
+ // return.
+ mCurrent = TextRenderedRun();
+ return mCurrent;
+ }
+
+ // The values we will use to initialize the TextRenderedRun with.
+ nsTextFrame* frame;
+ gfxPoint pt;
+ double rotate;
+ nscoord baseline;
+ uint32_t offset, length;
+ uint32_t charIndex;
+
+ // We loop, because we want to skip over rendered runs that either aren't
+ // within our subtree of interest, because they don't match the filter,
+ // or because they are hidden due to having fallen off the end of a
+ // <textPath>.
+ for (;;) {
+ if (mFrameIterator.IsAfterSubtree()) {
+ mCurrent = TextRenderedRun();
+ return mCurrent;
+ }
+
+ frame = mFrameIterator.Current();
+
+ charIndex = mTextElementCharIndex;
+
+ // Find the end of the rendered run, by looking through the
+ // SVGTextFrame's positions array until we find one that is recorded
+ // as a run boundary.
+ uint32_t runStart,
+ runEnd; // XXX Replace runStart with mTextElementCharIndex.
+ runStart = mTextElementCharIndex;
+ runEnd = runStart + 1;
+ while (runEnd < Root()->mPositions.Length() &&
+ !Root()->mPositions[runEnd].mRunBoundary) {
+ runEnd++;
+ }
+
+ // Convert the global run start/end indexes into an offset/length into the
+ // current frame's Text.
+ offset =
+ frame->GetContentOffset() + runStart - mFrameStartTextElementCharIndex;
+ length = runEnd - runStart;
+
+ // If the end of the frame's content comes before the run boundary we found
+ // in SVGTextFrame's position array, we need to shorten the rendered run.
+ uint32_t contentEnd = frame->GetContentEnd();
+ if (offset + length > contentEnd) {
+ length = contentEnd - offset;
+ }
+
+ NS_ASSERTION(offset >= uint32_t(frame->GetContentOffset()),
+ "invalid offset");
+ NS_ASSERTION(offset + length <= contentEnd, "invalid offset or length");
+
+ // Get the frame's baseline position.
+ frame->EnsureTextRun(nsTextFrame::eInflated);
+ baseline = GetBaselinePosition(
+ frame, frame->GetTextRun(nsTextFrame::eInflated),
+ mFrameIterator.DominantBaseline(), mFontSizeScaleFactor);
+
+ // Trim the offset/length to remove any leading/trailing white space.
+ uint32_t untrimmedOffset = offset;
+ uint32_t untrimmedLength = length;
+ nsTextFrame::TrimmedOffsets trimmedOffsets =
+ frame->GetTrimmedOffsets(frame->TextFragment());
+ TrimOffsets(offset, length, trimmedOffsets);
+ charIndex += offset - untrimmedOffset;
+
+ // Get the position and rotation of the character that begins this
+ // rendered run.
+ pt = Root()->mPositions[charIndex].mPosition;
+ rotate = Root()->mPositions[charIndex].mAngle;
+
+ // Determine if we should skip this rendered run.
+ bool skip = !mFrameIterator.IsWithinSubtree() ||
+ Root()->mPositions[mTextElementCharIndex].mHidden;
+ if (mFilter == eVisibleFrames) {
+ skip = skip || !frame->StyleVisibility()->IsVisible();
+ }
+
+ // Update our global character index to move past the characters
+ // corresponding to this rendered run.
+ mTextElementCharIndex += untrimmedLength;
+
+ // If we have moved past the end of the current frame's content, we need to
+ // advance to the next frame.
+ if (offset + untrimmedLength >= contentEnd) {
+ mFrameIterator.Next();
+ mTextElementCharIndex += mFrameIterator.UndisplayedCharacters();
+ mFrameStartTextElementCharIndex = mTextElementCharIndex;
+ }
+
+ if (!mFrameIterator.Current()) {
+ if (skip) {
+ // That was the last frame, and we skipped this rendered run. So we
+ // have no rendered run to return.
+ mCurrent = TextRenderedRun();
+ return mCurrent;
+ }
+ break;
+ }
+
+ if (length && !skip) {
+ // Only return a rendered run if it didn't get collapsed away entirely
+ // (due to it being all white space) and if we don't want to skip it.
+ break;
+ }
+ }
+
+ mCurrent = TextRenderedRun(frame, pt, Root()->mLengthAdjustScaleFactor,
+ rotate, mFontSizeScaleFactor, baseline, offset,
+ length, charIndex);
+ return mCurrent;
+}
+
+TextRenderedRun TextRenderedRunIterator::First() {
+ if (!mFrameIterator.Current()) {
+ return TextRenderedRun();
+ }
+
+ if (Root()->mPositions.IsEmpty()) {
+ mFrameIterator.Close();
+ return TextRenderedRun();
+ }
+
+ // Get the character index for the start of this rendered run, by skipping
+ // any undisplayed characters.
+ mTextElementCharIndex = mFrameIterator.UndisplayedCharacters();
+ mFrameStartTextElementCharIndex = mTextElementCharIndex;
+
+ return Next();
+}
+
+// -----------------------------------------------------------------------------
+// CharIterator
+
+/**
+ * Iterator for characters within an SVGTextFrame.
+ */
+class MOZ_STACK_CLASS CharIterator {
+ using Range = gfxTextRun::Range;
+
+ public:
+ /**
+ * Values for the aFilter argument of the constructor, to indicate which
+ * characters we should be iterating over.
+ */
+ enum CharacterFilter {
+ // Iterate over all original characters from the DOM that are within valid
+ // text content elements.
+ eOriginal,
+ // Iterate only over characters that are not skipped characters.
+ eUnskipped,
+ // Iterate only over characters that are addressable by the positioning
+ // attributes x="", y="", etc. This includes all characters after
+ // collapsing white space as required by the value of 'white-space'.
+ eAddressable,
+ };
+
+ /**
+ * Constructs a CharIterator.
+ *
+ * @param aSVGTextFrame The SVGTextFrame whose characters to iterate
+ * through.
+ * @param aFilter Indicates which characters to iterate over.
+ * @param aSubtree A content subtree to track whether the current character
+ * is within.
+ */
+ CharIterator(SVGTextFrame* aSVGTextFrame, CharacterFilter aFilter,
+ nsIContent* aSubtree, bool aPostReflow = true);
+
+ /**
+ * Returns whether the iterator is finished.
+ */
+ bool AtEnd() const { return !mFrameIterator.Current(); }
+
+ /**
+ * Advances to the next matching character. Returns true if there was a
+ * character to advance to, and false otherwise.
+ */
+ bool Next();
+
+ /**
+ * Advances ahead aCount matching characters. Returns true if there were
+ * enough characters to advance past, and false otherwise.
+ */
+ bool Next(uint32_t aCount);
+
+ /**
+ * Advances ahead up to aCount matching characters.
+ */
+ void NextWithinSubtree(uint32_t aCount);
+
+ /**
+ * Advances to the character with the specified index. The index is in the
+ * space of original characters (i.e., all DOM characters under the <text>
+ * that are within valid text content elements).
+ */
+ bool AdvanceToCharacter(uint32_t aTextElementCharIndex);
+
+ /**
+ * Advances to the first matching character after the current nsTextFrame.
+ */
+ bool AdvancePastCurrentFrame();
+
+ /**
+ * Advances to the first matching character after the frames within
+ * the current <textPath>.
+ */
+ bool AdvancePastCurrentTextPathFrame();
+
+ /**
+ * Advances to the first matching character of the subtree. Returns true
+ * if we successfully advance to the subtree, or if we are already within
+ * the subtree. Returns false if we are past the subtree.
+ */
+ bool AdvanceToSubtree();
+
+ /**
+ * Returns the nsTextFrame for the current character.
+ */
+ nsTextFrame* TextFrame() const { return mFrameIterator.Current(); }
+
+ /**
+ * Returns whether the iterator is within the subtree.
+ */
+ bool IsWithinSubtree() const { return mFrameIterator.IsWithinSubtree(); }
+
+ /**
+ * Returns whether the iterator is past the subtree.
+ */
+ bool IsAfterSubtree() const { return mFrameIterator.IsAfterSubtree(); }
+
+ /**
+ * Returns whether the current character is a skipped character.
+ */
+ bool IsOriginalCharSkipped() const {
+ return mSkipCharsIterator.IsOriginalCharSkipped();
+ }
+
+ /**
+ * Returns whether the current character is the start of a cluster and
+ * ligature group.
+ */
+ bool IsClusterAndLigatureGroupStart() const;
+
+ /**
+ * Returns the glyph run for the current character.
+ */
+ const gfxTextRun::GlyphRun& GlyphRun() const;
+
+ /**
+ * Returns whether the current character is trimmed away when painting,
+ * due to it being leading/trailing white space.
+ */
+ bool IsOriginalCharTrimmed() const;
+
+ /**
+ * Returns whether the current character is unaddressable from the SVG glyph
+ * positioning attributes.
+ */
+ bool IsOriginalCharUnaddressable() const {
+ return IsOriginalCharSkipped() || IsOriginalCharTrimmed();
+ }
+
+ /**
+ * Returns the text run for the current character.
+ */
+ gfxTextRun* TextRun() const { return mTextRun; }
+
+ /**
+ * Returns the current character index.
+ */
+ uint32_t TextElementCharIndex() const { return mTextElementCharIndex; }
+
+ /**
+ * Returns the character index for the start of the cluster/ligature group it
+ * is part of.
+ */
+ uint32_t GlyphStartTextElementCharIndex() const {
+ return mGlyphStartTextElementCharIndex;
+ }
+
+ /**
+ * Gets the advance, in user units, of the current character. If the
+ * character is a part of ligature, then the advance returned will be
+ * a fraction of the ligature glyph's advance.
+ *
+ * @param aContext The context to use for unit conversions.
+ */
+ gfxFloat GetAdvance(nsPresContext* aContext) const;
+
+ /**
+ * Returns the frame corresponding to the <textPath> that the current
+ * character is within.
+ */
+ nsIFrame* TextPathFrame() const { return mFrameIterator.TextPathFrame(); }
+
+#ifdef DEBUG
+ /**
+ * Returns the subtree we were constructed with.
+ */
+ nsIContent* GetSubtree() const { return mSubtree; }
+
+ /**
+ * Returns the CharacterFilter mode in use.
+ */
+ CharacterFilter Filter() const { return mFilter; }
+#endif
+
+ private:
+ /**
+ * Advances to the next character without checking it against the filter.
+ * Returns true if there was a next character to advance to, or false
+ * otherwise.
+ */
+ bool NextCharacter();
+
+ /**
+ * Returns whether the current character matches the filter.
+ */
+ bool MatchesFilter() const;
+
+ /**
+ * If this is the start of a glyph, record it.
+ */
+ void UpdateGlyphStartTextElementCharIndex() {
+ if (!IsOriginalCharSkipped() && IsClusterAndLigatureGroupStart()) {
+ mGlyphStartTextElementCharIndex = mTextElementCharIndex;
+ }
+ }
+
+ /**
+ * The filter to use.
+ */
+ CharacterFilter mFilter;
+
+ /**
+ * The iterator for text frames.
+ */
+ TextFrameIterator mFrameIterator;
+
+#ifdef DEBUG
+ /**
+ * The subtree we were constructed with.
+ */
+ nsIContent* const mSubtree;
+#endif
+
+ /**
+ * A gfxSkipCharsIterator for the text frame the current character is
+ * a part of.
+ */
+ gfxSkipCharsIterator mSkipCharsIterator;
+
+ // Cache for information computed by IsOriginalCharTrimmed.
+ mutable nsTextFrame* mFrameForTrimCheck;
+ mutable uint32_t mTrimmedOffset;
+ mutable uint32_t mTrimmedLength;
+
+ /**
+ * The text run the current character is a part of.
+ */
+ gfxTextRun* mTextRun;
+
+ /**
+ * The current character's index.
+ */
+ uint32_t mTextElementCharIndex;
+
+ /**
+ * The index of the character that starts the cluster/ligature group the
+ * current character is a part of.
+ */
+ uint32_t mGlyphStartTextElementCharIndex;
+
+ /**
+ * The scale factor to apply to glyph advances returned by
+ * GetAdvance etc. to take into account textLength="".
+ */
+ float mLengthAdjustScaleFactor;
+
+ /**
+ * Whether the instance of this class is being used after reflow has occurred
+ * or not.
+ */
+ bool mPostReflow;
+};
+
+CharIterator::CharIterator(SVGTextFrame* aSVGTextFrame,
+ CharIterator::CharacterFilter aFilter,
+ nsIContent* aSubtree, bool aPostReflow)
+ : mFilter(aFilter),
+ mFrameIterator(aSVGTextFrame, aSubtree),
+#ifdef DEBUG
+ mSubtree(aSubtree),
+#endif
+ mFrameForTrimCheck(nullptr),
+ mTrimmedOffset(0),
+ mTrimmedLength(0),
+ mTextRun(nullptr),
+ mTextElementCharIndex(0),
+ mGlyphStartTextElementCharIndex(0),
+ mLengthAdjustScaleFactor(aSVGTextFrame->mLengthAdjustScaleFactor),
+ mPostReflow(aPostReflow) {
+ if (!AtEnd()) {
+ mSkipCharsIterator = TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
+ mTextRun = TextFrame()->GetTextRun(nsTextFrame::eInflated);
+ mTextElementCharIndex = mFrameIterator.UndisplayedCharacters();
+ UpdateGlyphStartTextElementCharIndex();
+ if (!MatchesFilter()) {
+ Next();
+ }
+ }
+}
+
+bool CharIterator::Next() {
+ while (NextCharacter()) {
+ if (MatchesFilter()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CharIterator::Next(uint32_t aCount) {
+ if (aCount == 0 && AtEnd()) {
+ return false;
+ }
+ while (aCount) {
+ if (!Next()) {
+ return false;
+ }
+ aCount--;
+ }
+ return true;
+}
+
+void CharIterator::NextWithinSubtree(uint32_t aCount) {
+ while (IsWithinSubtree() && aCount) {
+ --aCount;
+ if (!Next()) {
+ return;
+ }
+ }
+}
+
+bool CharIterator::AdvanceToCharacter(uint32_t aTextElementCharIndex) {
+ while (mTextElementCharIndex < aTextElementCharIndex) {
+ if (!Next()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool CharIterator::AdvancePastCurrentFrame() {
+ // XXX Can do this better than one character at a time if it matters.
+ nsTextFrame* currentFrame = TextFrame();
+ do {
+ if (!Next()) {
+ return false;
+ }
+ } while (TextFrame() == currentFrame);
+ return true;
+}
+
+bool CharIterator::AdvancePastCurrentTextPathFrame() {
+ nsIFrame* currentTextPathFrame = TextPathFrame();
+ NS_ASSERTION(currentTextPathFrame,
+ "expected AdvancePastCurrentTextPathFrame to be called only "
+ "within a text path frame");
+ do {
+ if (!AdvancePastCurrentFrame()) {
+ return false;
+ }
+ } while (TextPathFrame() == currentTextPathFrame);
+ return true;
+}
+
+bool CharIterator::AdvanceToSubtree() {
+ while (!IsWithinSubtree()) {
+ if (IsAfterSubtree()) {
+ return false;
+ }
+ if (!AdvancePastCurrentFrame()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool CharIterator::IsClusterAndLigatureGroupStart() const {
+ return mTextRun->IsLigatureGroupStart(
+ mSkipCharsIterator.GetSkippedOffset()) &&
+ mTextRun->IsClusterStart(mSkipCharsIterator.GetSkippedOffset());
+}
+
+const gfxTextRun::GlyphRun& CharIterator::GlyphRun() const {
+ uint32_t numRuns;
+ const gfxTextRun::GlyphRun* glyphRuns = mTextRun->GetGlyphRuns(&numRuns);
+ uint32_t runIndex = mTextRun->FindFirstGlyphRunContaining(
+ mSkipCharsIterator.GetSkippedOffset());
+ MOZ_ASSERT(runIndex < numRuns);
+ return glyphRuns[runIndex];
+}
+
+bool CharIterator::IsOriginalCharTrimmed() const {
+ if (mFrameForTrimCheck != TextFrame()) {
+ // Since we do a lot of trim checking, we cache the trimmed offsets and
+ // lengths while we are in the same frame.
+ mFrameForTrimCheck = TextFrame();
+ uint32_t offset = mFrameForTrimCheck->GetContentOffset();
+ uint32_t length = mFrameForTrimCheck->GetContentLength();
+ nsTextFrame::TrimmedOffsets trim = mFrameForTrimCheck->GetTrimmedOffsets(
+ mFrameForTrimCheck->TextFragment(),
+ (mPostReflow ? nsTextFrame::TrimmedOffsetFlags::Default
+ : nsTextFrame::TrimmedOffsetFlags::NotPostReflow));
+ TrimOffsets(offset, length, trim);
+ mTrimmedOffset = offset;
+ mTrimmedLength = length;
+ }
+
+ // A character is trimmed if it is outside the mTrimmedOffset/mTrimmedLength
+ // range and it is not a significant newline character.
+ uint32_t index = mSkipCharsIterator.GetOriginalOffset();
+ return !(
+ (index >= mTrimmedOffset && index < mTrimmedOffset + mTrimmedLength) ||
+ (index >= mTrimmedOffset + mTrimmedLength &&
+ mFrameForTrimCheck->StyleText()->NewlineIsSignificant(
+ mFrameForTrimCheck) &&
+ mFrameForTrimCheck->TextFragment()->CharAt(index) == '\n'));
+}
+
+gfxFloat CharIterator::GetAdvance(nsPresContext* aContext) const {
+ float cssPxPerDevPx =
+ nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
+
+ gfxSkipCharsIterator start =
+ TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
+ nsTextFrame::PropertyProvider provider(TextFrame(), start);
+
+ uint32_t offset = mSkipCharsIterator.GetSkippedOffset();
+ gfxFloat advance =
+ mTextRun->GetAdvanceWidth(Range(offset, offset + 1), &provider);
+ return aContext->AppUnitsToGfxUnits(advance) * mLengthAdjustScaleFactor *
+ cssPxPerDevPx;
+}
+
+bool CharIterator::NextCharacter() {
+ if (AtEnd()) {
+ return false;
+ }
+
+ mTextElementCharIndex++;
+
+ // Advance within the current text run.
+ mSkipCharsIterator.AdvanceOriginal(1);
+ if (mSkipCharsIterator.GetOriginalOffset() < TextFrame()->GetContentEnd()) {
+ // We're still within the part of the text run for the current text frame.
+ UpdateGlyphStartTextElementCharIndex();
+ return true;
+ }
+
+ // Advance to the next frame.
+ mFrameIterator.Next();
+
+ // Skip any undisplayed characters.
+ uint32_t undisplayed = mFrameIterator.UndisplayedCharacters();
+ mTextElementCharIndex += undisplayed;
+ if (!TextFrame()) {
+ // We're at the end.
+ mSkipCharsIterator = gfxSkipCharsIterator();
+ return false;
+ }
+
+ mSkipCharsIterator = TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
+ mTextRun = TextFrame()->GetTextRun(nsTextFrame::eInflated);
+ UpdateGlyphStartTextElementCharIndex();
+ return true;
+}
+
+bool CharIterator::MatchesFilter() const {
+ switch (mFilter) {
+ case eOriginal:
+ return true;
+ case eUnskipped:
+ return !IsOriginalCharSkipped();
+ case eAddressable:
+ return !IsOriginalCharSkipped() && !IsOriginalCharUnaddressable();
+ }
+ MOZ_ASSERT_UNREACHABLE("Invalid mFilter value");
+ return true;
+}
+
+// -----------------------------------------------------------------------------
+// SVGTextDrawPathCallbacks
+
+/**
+ * Text frame draw callback class that paints the text and text decoration parts
+ * of an nsTextFrame using SVG painting properties, and selection backgrounds
+ * and decorations as they would normally.
+ *
+ * An instance of this class is passed to nsTextFrame::PaintText if painting
+ * cannot be done directly (e.g. if we are using an SVG pattern fill, stroking
+ * the text, etc.).
+ */
+class SVGTextDrawPathCallbacks final : public nsTextFrame::DrawPathCallbacks {
+ using imgDrawingParams = image::imgDrawingParams;
+
+ public:
+ /**
+ * Constructs an SVGTextDrawPathCallbacks.
+ *
+ * @param aSVGTextFrame The ancestor text frame.
+ * @param aContext The context to use for painting.
+ * @param aFrame The nsTextFrame to paint.
+ * @param aCanvasTM The transformation matrix to set when painting; this
+ * should be the FOR_OUTERSVG_TM canvas TM of the text, so that
+ * paint servers are painted correctly.
+ * @param aImgParams Whether we need to synchronously decode images.
+ * @param aShouldPaintSVGGlyphs Whether SVG glyphs should be painted.
+ */
+ SVGTextDrawPathCallbacks(SVGTextFrame* aSVGTextFrame, gfxContext& aContext,
+ nsTextFrame* aFrame, const gfxMatrix& aCanvasTM,
+ imgDrawingParams& aImgParams,
+ bool aShouldPaintSVGGlyphs)
+ : DrawPathCallbacks(aShouldPaintSVGGlyphs),
+ mSVGTextFrame(aSVGTextFrame),
+ mContext(aContext),
+ mFrame(aFrame),
+ mCanvasTM(aCanvasTM),
+ mImgParams(aImgParams),
+ mColor(0) {}
+
+ void NotifySelectionBackgroundNeedsFill(const Rect& aBackgroundRect,
+ nscolor aColor,
+ DrawTarget& aDrawTarget) override;
+ void PaintDecorationLine(Rect aPath, nscolor aColor) override;
+ void PaintSelectionDecorationLine(Rect aPath, nscolor aColor) override;
+ void NotifyBeforeText(nscolor aColor) override;
+ void NotifyGlyphPathEmitted() override;
+ void NotifyAfterText() override;
+
+ private:
+ void SetupContext();
+
+ bool IsClipPathChild() const {
+ return mSVGTextFrame->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD);
+ }
+
+ /**
+ * Paints a piece of text geometry. This is called when glyphs
+ * or text decorations have been emitted to the gfxContext.
+ */
+ void HandleTextGeometry();
+
+ /**
+ * Sets the gfxContext paint to the appropriate color or pattern
+ * for filling text geometry.
+ */
+ void MakeFillPattern(GeneralPattern* aOutPattern);
+
+ /**
+ * Fills and strokes a piece of text geometry, using group opacity
+ * if the selection style requires it.
+ */
+ void FillAndStrokeGeometry();
+
+ /**
+ * Fills a piece of text geometry.
+ */
+ void FillGeometry();
+
+ /**
+ * Strokes a piece of text geometry.
+ */
+ void StrokeGeometry();
+
+ SVGTextFrame* const mSVGTextFrame;
+ gfxContext& mContext;
+ nsTextFrame* const mFrame;
+ const gfxMatrix& mCanvasTM;
+ imgDrawingParams& mImgParams;
+
+ /**
+ * The color that we were last told from one of the path callback functions.
+ * This color can be the special NS_SAME_AS_FOREGROUND_COLOR,
+ * NS_40PERCENT_FOREGROUND_COLOR and NS_TRANSPARENT colors when we are
+ * painting selections or IME decorations.
+ */
+ nscolor mColor;
+};
+
+void SVGTextDrawPathCallbacks::NotifySelectionBackgroundNeedsFill(
+ const Rect& aBackgroundRect, nscolor aColor, DrawTarget& aDrawTarget) {
+ if (IsClipPathChild()) {
+ // Don't paint selection backgrounds when in a clip path.
+ return;
+ }
+
+ mColor = aColor; // currently needed by MakeFillPattern
+
+ GeneralPattern fillPattern;
+ MakeFillPattern(&fillPattern);
+ if (fillPattern.GetPattern()) {
+ DrawOptions drawOptions(aColor == NS_40PERCENT_FOREGROUND_COLOR ? 0.4
+ : 1.0);
+ aDrawTarget.FillRect(aBackgroundRect, fillPattern, drawOptions);
+ }
+}
+
+void SVGTextDrawPathCallbacks::NotifyBeforeText(nscolor aColor) {
+ mColor = aColor;
+ SetupContext();
+ mContext.NewPath();
+}
+
+void SVGTextDrawPathCallbacks::NotifyGlyphPathEmitted() {
+ HandleTextGeometry();
+ mContext.NewPath();
+}
+
+void SVGTextDrawPathCallbacks::NotifyAfterText() { mContext.Restore(); }
+
+void SVGTextDrawPathCallbacks::PaintDecorationLine(Rect aPath, nscolor aColor) {
+ mColor = aColor;
+ AntialiasMode aaMode =
+ SVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering);
+
+ mContext.Save();
+ mContext.NewPath();
+ mContext.SetAntialiasMode(aaMode);
+ mContext.Rectangle(ThebesRect(aPath));
+ HandleTextGeometry();
+ mContext.NewPath();
+ mContext.Restore();
+}
+
+void SVGTextDrawPathCallbacks::PaintSelectionDecorationLine(Rect aPath,
+ nscolor aColor) {
+ if (IsClipPathChild()) {
+ // Don't paint selection decorations when in a clip path.
+ return;
+ }
+
+ mColor = aColor;
+
+ mContext.Save();
+ mContext.NewPath();
+ mContext.Rectangle(ThebesRect(aPath));
+ FillAndStrokeGeometry();
+ mContext.Restore();
+}
+
+void SVGTextDrawPathCallbacks::SetupContext() {
+ mContext.Save();
+
+ // XXX This is copied from nsSVGGlyphFrame::Render, but cairo doesn't actually
+ // seem to do anything with the antialias mode. So we can perhaps remove it,
+ // or make SetAntialiasMode set cairo text antialiasing too.
+ switch (mFrame->StyleText()->mTextRendering) {
+ case StyleTextRendering::Optimizespeed:
+ mContext.SetAntialiasMode(AntialiasMode::NONE);
+ break;
+ default:
+ mContext.SetAntialiasMode(AntialiasMode::SUBPIXEL);
+ break;
+ }
+}
+
+void SVGTextDrawPathCallbacks::HandleTextGeometry() {
+ if (IsClipPathChild()) {
+ RefPtr<Path> path = mContext.GetPath();
+ ColorPattern white(
+ DeviceColor(1.f, 1.f, 1.f, 1.f)); // for masking, so no ToDeviceColor
+ mContext.GetDrawTarget()->Fill(path, white);
+ } else {
+ // Normal painting.
+ gfxContextMatrixAutoSaveRestore saveMatrix(&mContext);
+ mContext.SetMatrixDouble(mCanvasTM);
+
+ FillAndStrokeGeometry();
+ }
+}
+
+void SVGTextDrawPathCallbacks::MakeFillPattern(GeneralPattern* aOutPattern) {
+ if (mColor == NS_SAME_AS_FOREGROUND_COLOR ||
+ mColor == NS_40PERCENT_FOREGROUND_COLOR) {
+ SVGUtils::MakeFillPatternFor(mFrame, &mContext, aOutPattern, mImgParams);
+ return;
+ }
+
+ if (mColor == NS_TRANSPARENT) {
+ return;
+ }
+
+ aOutPattern->InitColorPattern(ToDeviceColor(mColor));
+}
+
+void SVGTextDrawPathCallbacks::FillAndStrokeGeometry() {
+ bool pushedGroup = false;
+ if (mColor == NS_40PERCENT_FOREGROUND_COLOR) {
+ pushedGroup = true;
+ mContext.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 0.4f);
+ }
+
+ uint32_t paintOrder = mFrame->StyleSVG()->mPaintOrder;
+ if (!paintOrder) {
+ FillGeometry();
+ StrokeGeometry();
+ } else {
+ while (paintOrder) {
+ auto component = StylePaintOrder(paintOrder & kPaintOrderMask);
+ switch (component) {
+ case StylePaintOrder::Fill:
+ FillGeometry();
+ break;
+ case StylePaintOrder::Stroke:
+ StrokeGeometry();
+ break;
+ default:
+ MOZ_FALLTHROUGH_ASSERT("Unknown paint-order value");
+ case StylePaintOrder::Markers:
+ case StylePaintOrder::Normal:
+ break;
+ }
+ paintOrder >>= kPaintOrderShift;
+ }
+ }
+
+ if (pushedGroup) {
+ mContext.PopGroupAndBlend();
+ }
+}
+
+void SVGTextDrawPathCallbacks::FillGeometry() {
+ GeneralPattern fillPattern;
+ MakeFillPattern(&fillPattern);
+ if (fillPattern.GetPattern()) {
+ RefPtr<Path> path = mContext.GetPath();
+ FillRule fillRule =
+ SVGUtils::ToFillRule(IsClipPathChild() ? mFrame->StyleSVG()->mClipRule
+ : mFrame->StyleSVG()->mFillRule);
+ if (fillRule != path->GetFillRule()) {
+ RefPtr<PathBuilder> builder = path->CopyToBuilder(fillRule);
+ path = builder->Finish();
+ }
+ mContext.GetDrawTarget()->Fill(path, fillPattern);
+ }
+}
+
+void SVGTextDrawPathCallbacks::StrokeGeometry() {
+ // We don't paint the stroke when we are filling with a selection color.
+ if (mColor == NS_SAME_AS_FOREGROUND_COLOR ||
+ mColor == NS_40PERCENT_FOREGROUND_COLOR) {
+ if (SVGUtils::HasStroke(mFrame, /*aContextPaint*/ nullptr)) {
+ GeneralPattern strokePattern;
+ SVGUtils::MakeStrokePatternFor(mFrame, &mContext, &strokePattern,
+ mImgParams, /*aContextPaint*/ nullptr);
+ if (strokePattern.GetPattern()) {
+ if (!mFrame->GetParent()->GetContent()->IsSVGElement()) {
+ // The cast that follows would be unsafe
+ MOZ_ASSERT(false, "Our nsTextFrame's parent's content should be SVG");
+ return;
+ }
+ SVGElement* svgOwner =
+ static_cast<SVGElement*>(mFrame->GetParent()->GetContent());
+
+ // Apply any stroke-specific transform
+ gfxMatrix outerSVGToUser;
+ if (SVGUtils::GetNonScalingStrokeTransform(mFrame, &outerSVGToUser) &&
+ outerSVGToUser.Invert()) {
+ mContext.Multiply(outerSVGToUser);
+ }
+
+ RefPtr<Path> path = mContext.GetPath();
+ SVGContentUtils::AutoStrokeOptions strokeOptions;
+ SVGContentUtils::GetStrokeOptions(&strokeOptions, svgOwner,
+ mFrame->Style(),
+ /*aContextPaint*/ nullptr);
+ DrawOptions drawOptions;
+ drawOptions.mAntialiasMode =
+ SVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering);
+ mContext.GetDrawTarget()->Stroke(path, strokePattern, strokeOptions);
+ }
+ }
+ }
+}
+
+// ============================================================================
+// SVGTextFrame
+
+// ----------------------------------------------------------------------------
+// Display list item
+
+class DisplaySVGText final : public nsPaintedDisplayItem {
+ public:
+ DisplaySVGText(nsDisplayListBuilder* aBuilder, SVGTextFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(DisplaySVGText);
+ MOZ_ASSERT(aFrame, "Must have a frame!");
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ MOZ_COUNTED_DTOR_OVERRIDE(DisplaySVGText)
+#endif
+
+ NS_DISPLAY_DECL_NAME("DisplaySVGText", TYPE_SVG_TEXT)
+
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) override;
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) override {
+ return new nsDisplayItemGenericGeometry(this, aBuilder);
+ }
+
+ virtual nsRect GetComponentAlphaBounds(
+ nsDisplayListBuilder* aBuilder) const override {
+ bool snap;
+ return GetBounds(aBuilder, &snap);
+ }
+};
+
+void DisplaySVGText::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect, HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) {
+ SVGTextFrame* frame = static_cast<SVGTextFrame*>(mFrame);
+ nsPoint pointRelativeToReferenceFrame = aRect.Center();
+ // ToReferenceFrame() includes frame->GetPosition(), our user space position.
+ nsPoint userSpacePtInAppUnits = pointRelativeToReferenceFrame -
+ (ToReferenceFrame() - frame->GetPosition());
+
+ gfxPoint userSpacePt =
+ gfxPoint(userSpacePtInAppUnits.x, userSpacePtInAppUnits.y) /
+ AppUnitsPerCSSPixel();
+
+ nsIFrame* target = frame->GetFrameForPoint(userSpacePt);
+ if (target) {
+ aOutFrames->AppendElement(target);
+ }
+}
+
+void DisplaySVGText::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+ uint32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+
+ // ToReferenceFrame includes our mRect offset, but painting takes
+ // account of that too. To avoid double counting, we subtract that
+ // here.
+ nsPoint offset = ToReferenceFrame() - mFrame->GetPosition();
+
+ gfxPoint devPixelOffset =
+ nsLayoutUtils::PointToGfxPoint(offset, appUnitsPerDevPixel);
+
+ gfxMatrix tm = SVGUtils::GetCSSPxToDevPxMatrix(mFrame) *
+ gfxMatrix::Translation(devPixelOffset);
+
+ gfxContext* ctx = aCtx;
+ imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags());
+ static_cast<SVGTextFrame*>(mFrame)->PaintSVG(*ctx, tm, imgParams);
+}
+
+// ---------------------------------------------------------------------
+// nsQueryFrame methods
+
+NS_QUERYFRAME_HEAD(SVGTextFrame)
+ NS_QUERYFRAME_ENTRY(SVGTextFrame)
+NS_QUERYFRAME_TAIL_INHERITING(SVGDisplayContainerFrame)
+
+} // namespace mozilla
+
+// ---------------------------------------------------------------------
+// Implementation
+
+nsIFrame* NS_NewSVGTextFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGTextFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGTextFrame)
+
+// ---------------------------------------------------------------------
+// nsIFrame methods
+
+void SVGTextFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::text),
+ "Content is not an SVG text");
+
+ SVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow);
+ AddStateBits((aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) |
+ NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_SVG_TEXT);
+
+ mMutationObserver = new MutationObserver(this);
+
+ if (mState & NS_FRAME_IS_NONDISPLAY) {
+ // We're inserting a new <text> element into a non-display context.
+ // Ensure that we get reflowed.
+ ScheduleReflowSVGNonDisplayText(
+ IntrinsicDirty::FrameAncestorsAndDescendants);
+ }
+}
+
+void SVGTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (IsSubtreeDirty()) {
+ // We can sometimes be asked to paint before reflow happens and we
+ // have updated mPositions, etc. In this case, we just avoid
+ // painting.
+ return;
+ }
+ if (!IsVisibleForPainting() && aBuilder->IsForPainting()) {
+ return;
+ }
+ DisplayOutline(aBuilder, aLists);
+ aLists.Content()->AppendNewToTop<DisplaySVGText>(aBuilder, this);
+}
+
+nsresult SVGTextFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType) {
+ if (aNameSpaceID != kNameSpaceID_None) {
+ return NS_OK;
+ }
+
+ if (aAttribute == nsGkAtoms::transform) {
+ // We don't invalidate for transform changes (the layers code does that).
+ // Also note that SVGTransformableElement::GetAttributeChangeHint will
+ // return nsChangeHint_UpdateOverflow for "transform" attribute changes
+ // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
+
+ if (!(mState & NS_FRAME_FIRST_REFLOW) && mCanvasTM &&
+ mCanvasTM->IsSingular()) {
+ // We won't have calculated the glyph positions correctly.
+ NotifyGlyphMetricsChange();
+ }
+ mCanvasTM = nullptr;
+ } else if (IsGlyphPositioningAttribute(aAttribute) ||
+ aAttribute == nsGkAtoms::textLength ||
+ aAttribute == nsGkAtoms::lengthAdjust) {
+ NotifyGlyphMetricsChange();
+ }
+
+ return NS_OK;
+}
+
+void SVGTextFrame::ReflowSVGNonDisplayText() {
+ MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this),
+ "only call ReflowSVGNonDisplayText when an outer SVG frame is "
+ "under ReflowSVG");
+ MOZ_ASSERT(mState & NS_FRAME_IS_NONDISPLAY,
+ "only call ReflowSVGNonDisplayText if the frame is "
+ "NS_FRAME_IS_NONDISPLAY");
+
+ // We had a style change, so we mark this frame as dirty so that the next
+ // time it is painted, we reflow the anonymous block frame.
+ this->MarkSubtreeDirty();
+
+ // Finally, we need to actually reflow the anonymous block frame and update
+ // mPositions, in case we are being reflowed immediately after a DOM
+ // mutation that needs frame reconstruction.
+ MaybeReflowAnonymousBlockChild();
+ UpdateGlyphPositioning();
+}
+
+void SVGTextFrame::ScheduleReflowSVGNonDisplayText(IntrinsicDirty aReason) {
+ MOZ_ASSERT(!SVGUtils::OuterSVGIsCallingReflowSVG(this),
+ "do not call ScheduleReflowSVGNonDisplayText when the outer SVG "
+ "frame is under ReflowSVG");
+ MOZ_ASSERT(!(mState & NS_STATE_SVG_TEXT_IN_REFLOW),
+ "do not call ScheduleReflowSVGNonDisplayText while reflowing the "
+ "anonymous block child");
+
+ // We need to find an ancestor frame that we can call FrameNeedsReflow
+ // on that will cause the document to be marked as needing relayout,
+ // and for that ancestor (or some further ancestor) to be marked as
+ // a root to reflow. We choose the closest ancestor frame that is not
+ // NS_FRAME_IS_NONDISPLAY and which is either an outer SVG frame or a
+ // non-SVG frame. (We don't consider displayed SVG frame ancestors other
+ // than SVGOuterSVGFrame, since calling FrameNeedsReflow on those other
+ // SVG frames would do a bunch of unnecessary work on the SVG frames up to
+ // the SVGOuterSVGFrame.)
+
+ nsIFrame* f = this;
+ while (f) {
+ if (!f->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ if (f->IsSubtreeDirty()) {
+ // This is a displayed frame, so if it is already dirty, we will be
+ // reflowed soon anyway. No need to call FrameNeedsReflow again, then.
+ return;
+ }
+ if (!f->IsFrameOfType(eSVG) || f->IsSVGOuterSVGFrame()) {
+ break;
+ }
+ f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+ f = f->GetParent();
+ }
+
+ MOZ_ASSERT(f, "should have found an ancestor frame to reflow");
+
+ PresShell()->FrameNeedsReflow(f, aReason, NS_FRAME_IS_DIRTY);
+}
+
+NS_IMPL_ISUPPORTS(SVGTextFrame::MutationObserver, nsIMutationObserver)
+
+void SVGTextFrame::MutationObserver::ContentAppended(
+ nsIContent* aFirstNewContent) {
+ mFrame->NotifyGlyphMetricsChange();
+}
+
+void SVGTextFrame::MutationObserver::ContentInserted(nsIContent* aChild) {
+ mFrame->NotifyGlyphMetricsChange();
+}
+
+void SVGTextFrame::MutationObserver::ContentRemoved(
+ nsIContent* aChild, nsIContent* aPreviousSibling) {
+ mFrame->NotifyGlyphMetricsChange();
+}
+
+void SVGTextFrame::MutationObserver::CharacterDataChanged(
+ nsIContent* aContent, const CharacterDataChangeInfo&) {
+ mFrame->NotifyGlyphMetricsChange();
+}
+
+void SVGTextFrame::MutationObserver::AttributeChanged(
+ Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType, const nsAttrValue* aOldValue) {
+ if (!aElement->IsSVGElement()) {
+ return;
+ }
+
+ // Attribute changes on this element will be handled by
+ // SVGTextFrame::AttributeChanged.
+ if (aElement == mFrame->GetContent()) {
+ return;
+ }
+
+ mFrame->HandleAttributeChangeInDescendant(aElement, aNameSpaceID, aAttribute);
+}
+
+void SVGTextFrame::HandleAttributeChangeInDescendant(Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute) {
+ if (aElement->IsSVGElement(nsGkAtoms::textPath)) {
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::startOffset ||
+ aAttribute == nsGkAtoms::path || aAttribute == nsGkAtoms::side_)) {
+ NotifyGlyphMetricsChange();
+ } else if ((aNameSpaceID == kNameSpaceID_XLink ||
+ aNameSpaceID == kNameSpaceID_None) &&
+ aAttribute == nsGkAtoms::href) {
+ // Blow away our reference, if any
+ nsIFrame* childElementFrame = aElement->GetPrimaryFrame();
+ if (childElementFrame) {
+ SVGObserverUtils::RemoveTextPathObserver(childElementFrame);
+ NotifyGlyphMetricsChange();
+ }
+ }
+ } else {
+ if (aNameSpaceID == kNameSpaceID_None &&
+ IsGlyphPositioningAttribute(aAttribute)) {
+ NotifyGlyphMetricsChange();
+ }
+ }
+}
+
+void SVGTextFrame::FindCloserFrameForSelection(
+ const nsPoint& aPoint, FrameWithDistance* aCurrentBestFrame) {
+ if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ return;
+ }
+
+ UpdateGlyphPositioning();
+
+ nsPresContext* presContext = PresContext();
+
+ // Find the frame that has the closest rendered run rect to aPoint.
+ TextRenderedRunIterator it(this);
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ uint32_t flags = TextRenderedRun::eIncludeFill |
+ TextRenderedRun::eIncludeStroke |
+ TextRenderedRun::eNoHorizontalOverflow;
+ SVGBBox userRect = run.GetUserSpaceRect(presContext, flags);
+ float devPxPerCSSPx = presContext->CSSPixelsToDevPixels(1.f);
+ userRect.Scale(devPxPerCSSPx);
+
+ if (!userRect.IsEmpty()) {
+ gfxMatrix m;
+ if (!NS_SVGDisplayListHitTestingEnabled()) {
+ m = GetCanvasTM();
+ }
+ nsRect rect =
+ SVGUtils::ToCanvasBounds(userRect.ToThebesRect(), m, presContext);
+
+ if (nsLayoutUtils::PointIsCloserToRect(aPoint, rect,
+ aCurrentBestFrame->mXDistance,
+ aCurrentBestFrame->mYDistance)) {
+ aCurrentBestFrame->mFrame = run.mFrame;
+ }
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+// ISVGDisplayableFrame methods
+
+void SVGTextFrame::NotifySVGChanged(uint32_t aFlags) {
+ MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
+ "Invalidation logic may need adjusting");
+
+ bool needNewBounds = false;
+ bool needGlyphMetricsUpdate = false;
+ bool needNewCanvasTM = false;
+
+ if ((aFlags & COORD_CONTEXT_CHANGED) &&
+ (mState & NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES)) {
+ needGlyphMetricsUpdate = true;
+ }
+
+ if (aFlags & TRANSFORM_CHANGED) {
+ needNewCanvasTM = true;
+ if (mCanvasTM && mCanvasTM->IsSingular()) {
+ // We won't have calculated the glyph positions correctly.
+ needNewBounds = true;
+ needGlyphMetricsUpdate = true;
+ }
+ if (StyleSVGReset()->HasNonScalingStroke()) {
+ // Stroke currently contributes to our mRect, and our stroke depends on
+ // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|.
+ needNewBounds = true;
+ }
+ }
+
+ // If the scale at which we computed our mFontSizeScaleFactor has changed by
+ // at least a factor of two, reflow the text. This avoids reflowing text
+ // at every tick of a transform animation, but ensures our glyph metrics
+ // do not get too far out of sync with the final font size on the screen.
+ if (needNewCanvasTM && mLastContextScale != 0.0f) {
+ mCanvasTM = nullptr;
+ // If we are a non-display frame, then we don't want to call
+ // GetCanvasTM(), since the context scale does not use it.
+ gfxMatrix newTM =
+ (mState & NS_FRAME_IS_NONDISPLAY) ? gfxMatrix() : GetCanvasTM();
+ // Compare the old and new context scales.
+ float scale = GetContextScale(newTM);
+ float change = scale / mLastContextScale;
+ if (change >= 2.0f || change <= 0.5f) {
+ needNewBounds = true;
+ needGlyphMetricsUpdate = true;
+ }
+ }
+
+ if (needNewBounds) {
+ // Ancestor changes can't affect how we render from the perspective of
+ // any rendering observers that we may have, so we don't need to
+ // invalidate them. We also don't need to invalidate ourself, since our
+ // changed ancestor will have invalidated its entire area, which includes
+ // our area.
+ ScheduleReflowSVG();
+ }
+
+ if (needGlyphMetricsUpdate) {
+ // If we are positioned using percentage values we need to update our
+ // position whenever our viewport's dimensions change. But only do this if
+ // we have been reflowed once, otherwise the glyph positioning will be
+ // wrong. (We need to wait until bidi reordering has been done.)
+ if (!(mState & NS_FRAME_FIRST_REFLOW)) {
+ NotifyGlyphMetricsChange();
+ }
+ }
+}
+
+/**
+ * Gets the offset into a DOM node that the specified caret is positioned at.
+ */
+static int32_t GetCaretOffset(nsCaret* aCaret) {
+ RefPtr<Selection> selection = aCaret->GetSelection();
+ if (!selection) {
+ return -1;
+ }
+
+ return selection->AnchorOffset();
+}
+
+/**
+ * Returns whether the caret should be painted for a given TextRenderedRun
+ * by checking whether the caret is in the range covered by the rendered run.
+ *
+ * @param aThisRun The TextRenderedRun to be painted.
+ * @param aCaret The caret.
+ */
+static bool ShouldPaintCaret(const TextRenderedRun& aThisRun, nsCaret* aCaret) {
+ int32_t caretOffset = GetCaretOffset(aCaret);
+
+ if (caretOffset < 0) {
+ return false;
+ }
+
+ return uint32_t(caretOffset) >= aThisRun.mTextFrameContentOffset &&
+ uint32_t(caretOffset) < aThisRun.mTextFrameContentOffset +
+ aThisRun.mTextFrameContentLength;
+}
+
+void SVGTextFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect) {
+ DrawTarget& aDrawTarget = *aContext.GetDrawTarget();
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (!kid) {
+ return;
+ }
+
+ nsPresContext* presContext = PresContext();
+
+ gfxMatrix initialMatrix = aContext.CurrentMatrixDouble();
+
+ if (mState & NS_FRAME_IS_NONDISPLAY) {
+ // If we are in a canvas DrawWindow call that used the
+ // DRAWWINDOW_DO_NOT_FLUSH flag, then we may still have out
+ // of date frames. Just don't paint anything if they are
+ // dirty.
+ if (presContext->PresShell()->InDrawWindowNotFlushing() &&
+ IsSubtreeDirty()) {
+ return;
+ }
+ // Text frames inside <clipPath>, <mask>, etc. will never have had
+ // ReflowSVG called on them, so call UpdateGlyphPositioning to do this now.
+ UpdateGlyphPositioning();
+ } else if (IsSubtreeDirty()) {
+ // If we are asked to paint before reflow has recomputed mPositions etc.
+ // directly via PaintSVG, rather than via a display list, then we need
+ // to bail out here too.
+ return;
+ }
+
+ const float epsilon = 0.0001;
+ if (abs(mLengthAdjustScaleFactor) < epsilon) {
+ // A zero scale factor can be caused by having forced the text length to
+ // zero. In this situation there is nothing to show.
+ return;
+ }
+
+ if (aTransform.IsSingular()) {
+ NS_WARNING("Can't render text element!");
+ return;
+ }
+
+ gfxMatrix matrixForPaintServers = aTransform * initialMatrix;
+
+ // Check if we need to draw anything.
+ if (aDirtyRect) {
+ NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "Display lists handle dirty rect intersection test");
+ nsRect dirtyRect(aDirtyRect->x, aDirtyRect->y, aDirtyRect->width,
+ aDirtyRect->height);
+
+ gfxFloat appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
+ gfxRect frameRect(
+ mRect.x / appUnitsPerDevPixel, mRect.y / appUnitsPerDevPixel,
+ mRect.width / appUnitsPerDevPixel, mRect.height / appUnitsPerDevPixel);
+
+ nsRect canvasRect = nsLayoutUtils::RoundGfxRectToAppRect(
+ GetCanvasTM().TransformBounds(frameRect), 1);
+ if (!canvasRect.Intersects(dirtyRect)) {
+ return;
+ }
+ }
+
+ // SVG frames' PaintSVG methods paint in CSS px, but normally frames paint in
+ // dev pixels. Here we multiply a CSS-px-to-dev-pixel factor onto aTransform
+ // so our non-SVG nsTextFrame children paint correctly.
+ auto auPerDevPx = presContext->AppUnitsPerDevPixel();
+ float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(auPerDevPx);
+ gfxMatrix canvasTMForChildren = aTransform;
+ canvasTMForChildren.PreScale(cssPxPerDevPx, cssPxPerDevPx);
+ initialMatrix.PreScale(1 / cssPxPerDevPx, 1 / cssPxPerDevPx);
+
+ gfxContextMatrixAutoSaveRestore matSR(&aContext);
+ aContext.NewPath();
+ aContext.Multiply(canvasTMForChildren);
+ gfxMatrix currentMatrix = aContext.CurrentMatrixDouble();
+
+ RefPtr<nsCaret> caret = presContext->PresShell()->GetCaret();
+ nsRect caretRect;
+ nsIFrame* caretFrame = caret->GetPaintGeometry(&caretRect);
+
+ gfxContextAutoSaveRestore ctxSR;
+ TextRenderedRunIterator it(this, TextRenderedRunIterator::eVisibleFrames);
+ TextRenderedRun run = it.Current();
+
+ SVGContextPaint* outerContextPaint =
+ SVGContextPaint::GetContextPaint(GetContent());
+
+ while (run.mFrame) {
+ nsTextFrame* frame = run.mFrame;
+
+ RefPtr<SVGContextPaintImpl> contextPaint = new SVGContextPaintImpl();
+ DrawMode drawMode = contextPaint->Init(&aDrawTarget, initialMatrix, frame,
+ outerContextPaint, aImgParams);
+ if (drawMode & DrawMode::GLYPH_STROKE) {
+ ctxSR.EnsureSaved(&aContext);
+ // This may change the gfxContext's transform (for non-scaling stroke),
+ // in which case this needs to happen before we call SetMatrix() below.
+ SVGUtils::SetupStrokeGeometry(frame->GetParent(), &aContext,
+ outerContextPaint);
+ }
+
+ nscoord startEdge, endEdge;
+ run.GetClipEdges(startEdge, endEdge);
+
+ // Set up the transform for painting the text frame for the substring
+ // indicated by the run.
+ gfxMatrix runTransform = run.GetTransformFromUserSpaceForPainting(
+ presContext, startEdge, endEdge) *
+ currentMatrix;
+ aContext.SetMatrixDouble(runTransform);
+
+ if (drawMode != DrawMode(0)) {
+ bool paintSVGGlyphs;
+ nsTextFrame::PaintTextParams params(&aContext);
+ params.framePt = Point();
+ params.dirtyRect =
+ LayoutDevicePixel::FromAppUnits(frame->InkOverflowRect(), auPerDevPx);
+ params.contextPaint = contextPaint;
+
+ const bool isSelected = frame->IsSelected();
+
+ if (ShouldRenderAsPath(frame, paintSVGGlyphs)) {
+ SVGTextDrawPathCallbacks callbacks(this, aContext, frame,
+ matrixForPaintServers, aImgParams,
+ paintSVGGlyphs);
+ params.callbacks = &callbacks;
+ frame->PaintText(params, startEdge, endEdge, nsPoint(), isSelected);
+ } else {
+ frame->PaintText(params, startEdge, endEdge, nsPoint(), isSelected);
+ }
+ }
+
+ if (frame == caretFrame && ShouldPaintCaret(run, caret)) {
+ // XXX Should we be looking at the fill/stroke colours to paint the
+ // caret with, rather than using the color property?
+ caret->PaintCaret(aDrawTarget, frame, nsPoint());
+ aContext.NewPath();
+ }
+
+ run = it.Next();
+ }
+}
+
+nsIFrame* SVGTextFrame::GetFrameForPoint(const gfxPoint& aPoint) {
+ NS_ASSERTION(PrincipalChildList().FirstChild(), "must have a child frame");
+
+ if (mState & NS_FRAME_IS_NONDISPLAY) {
+ // Text frames inside <clipPath> will never have had ReflowSVG called on
+ // them, so call UpdateGlyphPositioning to do this now. (Text frames
+ // inside <mask> and other non-display containers will never need to
+ // be hit tested.)
+ UpdateGlyphPositioning();
+ } else {
+ NS_ASSERTION(!IsSubtreeDirty(), "reflow should have happened");
+ }
+
+ // Hit-testing any clip-path will typically be a lot quicker than the
+ // hit-testing of our text frames in the loop below, so we do the former up
+ // front to avoid unnecessarily wasting cycles on the latter.
+ if (!SVGUtils::HitTestClip(this, aPoint)) {
+ return nullptr;
+ }
+
+ nsPresContext* presContext = PresContext();
+
+ // Ideally we'd iterate backwards so that we can just return the first frame
+ // that is under aPoint. In practice this will rarely matter though since it
+ // is rare for text in/under an SVG <text> element to overlap (i.e. the first
+ // text frame that is hit will likely be the only text frame that is hit).
+
+ TextRenderedRunIterator it(this);
+ nsIFrame* hit = nullptr;
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ uint16_t hitTestFlags = SVGUtils::GetGeometryHitTestFlags(run.mFrame);
+ if (!(hitTestFlags & (SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE))) {
+ continue;
+ }
+
+ gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext);
+ if (!m.Invert()) {
+ return nullptr;
+ }
+
+ gfxPoint pointInRunUserSpace = m.TransformPoint(aPoint);
+ gfxRect frameRect = run.GetRunUserSpaceRect(
+ presContext, TextRenderedRun::eIncludeFill |
+ TextRenderedRun::eIncludeStroke)
+ .ToThebesRect();
+
+ if (Inside(frameRect, pointInRunUserSpace)) {
+ hit = run.mFrame;
+ }
+ }
+ return hit;
+}
+
+void SVGTextFrame::ReflowSVG() {
+ MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this),
+ "This call is probaby a wasteful mistake");
+
+ MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
+ "ReflowSVG mechanism not designed for this");
+
+ if (!SVGUtils::NeedsReflowSVG(this)) {
+ MOZ_ASSERT(!HasAnyStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY |
+ NS_STATE_SVG_POSITIONING_DIRTY),
+ "How did this happen?");
+ return;
+ }
+
+ MaybeReflowAnonymousBlockChild();
+ UpdateGlyphPositioning();
+
+ nsPresContext* presContext = PresContext();
+
+ SVGBBox r;
+ TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames);
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ uint32_t runFlags = 0;
+ if (!run.mFrame->StyleSVG()->mFill.kind.IsNone()) {
+ runFlags |=
+ TextRenderedRun::eIncludeFill | TextRenderedRun::eIncludeTextShadow;
+ }
+ if (SVGUtils::HasStroke(run.mFrame)) {
+ runFlags |=
+ TextRenderedRun::eIncludeStroke | TextRenderedRun::eIncludeTextShadow;
+ }
+ // Our "visual" overflow rect needs to be valid for building display lists
+ // for hit testing, which means that for certain values of 'pointer-events'
+ // it needs to include the geometry of the fill or stroke even when the
+ // fill/ stroke don't actually render (e.g. when stroke="none" or
+ // stroke-opacity="0"). GetGeometryHitTestFlags accounts for
+ // 'pointer-events'. The text-shadow is not part of the hit-test area.
+ uint16_t hitTestFlags = SVGUtils::GetGeometryHitTestFlags(run.mFrame);
+ if (hitTestFlags & SVG_HIT_TEST_FILL) {
+ runFlags |= TextRenderedRun::eIncludeFill;
+ }
+ if (hitTestFlags & SVG_HIT_TEST_STROKE) {
+ runFlags |= TextRenderedRun::eIncludeStroke;
+ }
+
+ if (runFlags) {
+ r.UnionEdges(run.GetUserSpaceRect(presContext, runFlags));
+ }
+ }
+
+ if (r.IsEmpty()) {
+ mRect.SetEmpty();
+ } else {
+ mRect = nsLayoutUtils::RoundGfxRectToAppRect(r.ToThebesRect(),
+ AppUnitsPerCSSPixel());
+
+ // Due to rounding issues when we have a transform applied, we sometimes
+ // don't include an additional row of pixels. For now, just inflate our
+ // covered region.
+ mRect.Inflate(ceil(presContext->AppUnitsPerDevPixel() / mLastContextScale));
+ }
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ // Make sure we have our filter property (if any) before calling
+ // FinishAndStoreOverflow (subsequent filter changes are handled off
+ // nsChangeHint_UpdateEffects):
+ SVGObserverUtils::UpdateEffects(this);
+ }
+
+ // Now unset the various reflow bits. Do this before calling
+ // FinishAndStoreOverflow since FinishAndStoreOverflow can require glyph
+ // positions (to resolve transform-origin).
+ RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ nsRect overflow = nsRect(nsPoint(0, 0), mRect.Size());
+ OverflowAreas overflowAreas(overflow, overflow);
+ FinishAndStoreOverflow(overflowAreas, mRect.Size());
+
+ // XXX SVGContainerFrame::ReflowSVG only looks at its ISVGDisplayableFrame
+ // children, and calls ConsiderChildOverflow on them. Does it matter
+ // that ConsiderChildOverflow won't be called on our children?
+ SVGDisplayContainerFrame::ReflowSVG();
+}
+
+/**
+ * Converts SVGUtils::eBBox* flags into TextRenderedRun flags appropriate
+ * for the specified rendered run.
+ */
+static uint32_t TextRenderedRunFlagsForBBoxContribution(
+ const TextRenderedRun& aRun, uint32_t aBBoxFlags) {
+ uint32_t flags = 0;
+ if ((aBBoxFlags & SVGUtils::eBBoxIncludeFillGeometry) ||
+ ((aBBoxFlags & SVGUtils::eBBoxIncludeFill) &&
+ !aRun.mFrame->StyleSVG()->mFill.kind.IsNone())) {
+ flags |= TextRenderedRun::eIncludeFill;
+ }
+ if ((aBBoxFlags & SVGUtils::eBBoxIncludeStrokeGeometry) ||
+ ((aBBoxFlags & SVGUtils::eBBoxIncludeStroke) &&
+ SVGUtils::HasStroke(aRun.mFrame))) {
+ flags |= TextRenderedRun::eIncludeStroke;
+ }
+ return flags;
+}
+
+SVGBBox SVGTextFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace,
+ uint32_t aFlags) {
+ NS_ASSERTION(PrincipalChildList().FirstChild(), "must have a child frame");
+ SVGBBox bbox;
+
+ if (aFlags & SVGUtils::eForGetClientRects) {
+ Rect rect = NSRectToRect(mRect, AppUnitsPerCSSPixel());
+ if (!rect.IsEmpty()) {
+ bbox = aToBBoxUserspace.TransformBounds(rect);
+ }
+ return bbox;
+ }
+
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (kid && kid->IsSubtreeDirty()) {
+ // Return an empty bbox if our kid's subtree is dirty. This may be called
+ // in that situation, e.g. when we're building a display list after an
+ // interrupted reflow. This can also be called during reflow before we've
+ // been reflowed, e.g. if an earlier sibling is calling
+ // FinishAndStoreOverflow and needs our parent's perspective matrix, which
+ // depends on the SVG bbox contribution of this frame. In the latter
+ // situation, when all siblings have been reflowed, the parent will compute
+ // its perspective and rerun FinishAndStoreOverflow for all its children.
+ return bbox;
+ }
+
+ UpdateGlyphPositioning();
+
+ nsPresContext* presContext = PresContext();
+
+ TextRenderedRunIterator it(this);
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ uint32_t flags = TextRenderedRunFlagsForBBoxContribution(run, aFlags);
+ gfxMatrix m = ThebesMatrix(aToBBoxUserspace);
+ SVGBBox bboxForRun = run.GetUserSpaceRect(presContext, flags, &m);
+ bbox.UnionEdges(bboxForRun);
+ }
+
+ return bbox;
+}
+
+//----------------------------------------------------------------------
+// SVGTextFrame SVG DOM methods
+
+/**
+ * Returns whether the specified node has any non-empty Text
+ * beneath it.
+ */
+static bool HasTextContent(nsIContent* aContent) {
+ NS_ASSERTION(aContent, "expected non-null aContent");
+
+ TextNodeIterator it(aContent);
+ for (Text* text = it.Current(); text; text = it.Next()) {
+ if (text->TextLength() != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Returns the number of DOM characters beneath the specified node.
+ */
+static uint32_t GetTextContentLength(nsIContent* aContent) {
+ NS_ASSERTION(aContent, "expected non-null aContent");
+
+ uint32_t length = 0;
+ TextNodeIterator it(aContent);
+ for (Text* text = it.Current(); text; text = it.Next()) {
+ length += text->TextLength();
+ }
+ return length;
+}
+
+int32_t SVGTextFrame::ConvertTextElementCharIndexToAddressableIndex(
+ int32_t aIndex, nsIContent* aContent) {
+ CharIterator it(this, CharIterator::eOriginal, aContent);
+ if (!it.AdvanceToSubtree()) {
+ return -1;
+ }
+ int32_t result = 0;
+ int32_t textElementCharIndex;
+ while (!it.AtEnd() && it.IsWithinSubtree()) {
+ bool addressable = !it.IsOriginalCharUnaddressable();
+ textElementCharIndex = it.TextElementCharIndex();
+ it.Next();
+ uint32_t delta = it.TextElementCharIndex() - textElementCharIndex;
+ aIndex -= delta;
+ if (addressable) {
+ if (aIndex < 0) {
+ return result;
+ }
+ result += delta;
+ }
+ }
+ return -1;
+}
+
+/**
+ * Implements the SVG DOM GetNumberOfChars method for the specified
+ * text content element.
+ */
+uint32_t SVGTextFrame::GetNumberOfChars(nsIContent* aContent) {
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (kid->IsSubtreeDirty()) {
+ // We're never reflowed if we're under a non-SVG element that is
+ // never reflowed (such as the HTML 'caption' element).
+ return 0;
+ }
+
+ UpdateGlyphPositioning();
+
+ uint32_t n = 0;
+ CharIterator it(this, CharIterator::eAddressable, aContent);
+ if (it.AdvanceToSubtree()) {
+ while (!it.AtEnd() && it.IsWithinSubtree()) {
+ n++;
+ it.Next();
+ }
+ }
+ return n;
+}
+
+/**
+ * Implements the SVG DOM GetComputedTextLength method for the specified
+ * text child element.
+ */
+float SVGTextFrame::GetComputedTextLength(nsIContent* aContent) {
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (kid->IsSubtreeDirty()) {
+ // We're never reflowed if we're under a non-SVG element that is
+ // never reflowed (such as the HTML 'caption' element).
+ //
+ // If we ever decide that we need to return accurate values here,
+ // we could do similar work to GetSubStringLength.
+ return 0;
+ }
+
+ UpdateGlyphPositioning();
+
+ float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(
+ PresContext()->AppUnitsPerDevPixel());
+
+ nscoord length = 0;
+ TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames,
+ aContent);
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ length += run.GetAdvanceWidth();
+ }
+
+ return PresContext()->AppUnitsToGfxUnits(length) * cssPxPerDevPx *
+ mLengthAdjustScaleFactor / mFontSizeScaleFactor;
+}
+
+/**
+ * Implements the SVG DOM SelectSubString method for the specified
+ * text content element.
+ */
+void SVGTextFrame::SelectSubString(nsIContent* aContent, uint32_t charnum,
+ uint32_t nchars, ErrorResult& aRv) {
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (kid->IsSubtreeDirty()) {
+ // We're never reflowed if we're under a non-SVG element that is
+ // never reflowed (such as the HTML 'caption' element).
+ // XXXbz Should this just return without throwing like the no-frame case?
+ aRv.ThrowInvalidStateError("No layout information available for SVG text");
+ return;
+ }
+
+ UpdateGlyphPositioning();
+
+ // Convert charnum/nchars from addressable characters relative to
+ // aContent to global character indices.
+ CharIterator chit(this, CharIterator::eAddressable, aContent);
+ if (!chit.AdvanceToSubtree() || !chit.Next(charnum) ||
+ chit.IsAfterSubtree()) {
+ aRv.ThrowIndexSizeError("Character index out of range");
+ return;
+ }
+ charnum = chit.TextElementCharIndex();
+ const RefPtr<nsIContent> content = chit.TextFrame()->GetContent();
+ chit.NextWithinSubtree(nchars);
+ nchars = chit.TextElementCharIndex() - charnum;
+
+ RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
+
+ frameSelection->HandleClick(content, charnum, charnum + nchars,
+ nsFrameSelection::FocusMode::kCollapseToNewPoint,
+ CARET_ASSOCIATE_BEFORE);
+}
+
+/**
+ * Implements the SVG DOM GetSubStringLength method for the specified
+ * text content element.
+ */
+float SVGTextFrame::GetSubStringLength(nsIContent* aContent, uint32_t charnum,
+ uint32_t nchars, ErrorResult& aRv) {
+ // For some content we cannot (or currently cannot) compute the length
+ // without reflowing. In those cases we need to fall back to using
+ // GetSubStringLengthSlowFallback.
+ //
+ // We fall back for textPath since we need glyph positioning in order to
+ // tell if any characters should be ignored due to having fallen off the
+ // end of the textPath.
+ //
+ // We fall back for bidi because GetTrimmedOffsets does not produce the
+ // correct results for bidi continuations when passed aPostReflow = false.
+ // XXX It may be possible to determine which continuations to trim from (and
+ // which sides), but currently we don't do that. It would require us to
+ // identify the visual (rather than logical) start and end of the line, to
+ // avoid trimming at line-internal frame boundaries. Maybe nsBidiPresUtils
+ // methods like GetFrameToRightOf and GetFrameToLeftOf would help?
+ //
+ TextFrameIterator frameIter(this);
+ for (nsTextFrame* frame = frameIter.Current(); frame;
+ frame = frameIter.Next()) {
+ if (frameIter.TextPathFrame() || frame->GetNextContinuation()) {
+ return GetSubStringLengthSlowFallback(aContent, charnum, nchars, aRv);
+ }
+ }
+
+ // We only need our text correspondence to be up to date (no need to call
+ // UpdateGlyphPositioning).
+ TextNodeCorrespondenceRecorder::RecordCorrespondence(this);
+
+ // Convert charnum/nchars from addressable characters relative to
+ // aContent to global character indices.
+ CharIterator chit(this, CharIterator::eAddressable, aContent,
+ /* aPostReflow */ false);
+ if (!chit.AdvanceToSubtree() || !chit.Next(charnum) ||
+ chit.IsAfterSubtree()) {
+ aRv.ThrowIndexSizeError("Character index out of range");
+ return 0;
+ }
+
+ // We do this after the ThrowIndexSizeError() bit so JS calls correctly throw
+ // when necessary.
+ if (nchars == 0) {
+ return 0.0f;
+ }
+
+ charnum = chit.TextElementCharIndex();
+ chit.NextWithinSubtree(nchars);
+ nchars = chit.TextElementCharIndex() - charnum;
+
+ // Sum of the substring advances.
+ nscoord textLength = 0;
+
+ TextFrameIterator frit(this); // aSubtree = nullptr
+
+ // Index of the first non-skipped char in the frame, and of a subsequent char
+ // that we're interested in. Both are relative to the index of the first
+ // non-skipped char in the ancestor <text> element.
+ uint32_t frameStartTextElementCharIndex = 0;
+ uint32_t textElementCharIndex;
+
+ for (nsTextFrame* frame = frit.Current(); frame; frame = frit.Next()) {
+ frameStartTextElementCharIndex += frit.UndisplayedCharacters();
+ textElementCharIndex = frameStartTextElementCharIndex;
+
+ // Offset into frame's Text:
+ const uint32_t untrimmedOffset = frame->GetContentOffset();
+ const uint32_t untrimmedLength = frame->GetContentEnd() - untrimmedOffset;
+
+ // Trim the offset/length to remove any leading/trailing white space.
+ uint32_t trimmedOffset = untrimmedOffset;
+ uint32_t trimmedLength = untrimmedLength;
+ nsTextFrame::TrimmedOffsets trimmedOffsets = frame->GetTrimmedOffsets(
+ frame->TextFragment(), nsTextFrame::TrimmedOffsetFlags::NotPostReflow);
+ TrimOffsets(trimmedOffset, trimmedLength, trimmedOffsets);
+
+ textElementCharIndex += trimmedOffset - untrimmedOffset;
+
+ if (textElementCharIndex >= charnum + nchars) {
+ break; // we're past the end of the substring
+ }
+
+ uint32_t offset = textElementCharIndex;
+
+ // Intersect the substring we are interested in with the range covered by
+ // the nsTextFrame.
+ IntersectInterval(offset, trimmedLength, charnum, nchars);
+
+ if (trimmedLength != 0) {
+ // Convert offset into an index into the frame.
+ offset += trimmedOffset - textElementCharIndex;
+
+ gfxSkipCharsIterator it = frame->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = frame->GetTextRun(nsTextFrame::eInflated);
+ nsTextFrame::PropertyProvider provider(frame, it);
+
+ Range range = ConvertOriginalToSkipped(it, offset, trimmedLength);
+
+ // Accumulate the advance.
+ textLength += textRun->GetAdvanceWidth(range, &provider);
+ }
+
+ // Advance, ready for next call:
+ frameStartTextElementCharIndex += untrimmedLength;
+ }
+
+ nsPresContext* presContext = PresContext();
+ float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(
+ presContext->AppUnitsPerDevPixel());
+
+ return presContext->AppUnitsToGfxUnits(textLength) * cssPxPerDevPx /
+ mFontSizeScaleFactor;
+}
+
+float SVGTextFrame::GetSubStringLengthSlowFallback(nsIContent* aContent,
+ uint32_t charnum,
+ uint32_t nchars,
+ ErrorResult& aRv) {
+ // We need to make sure that we've been reflowed before updating the glyph
+ // positioning.
+ // 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.
+ RefPtr<mozilla::PresShell> presShell = PresShell();
+ presShell->FlushPendingNotifications(FlushType::Layout);
+
+ UpdateGlyphPositioning();
+
+ // Convert charnum/nchars from addressable characters relative to
+ // aContent to global character indices.
+ CharIterator chit(this, CharIterator::eAddressable, aContent);
+ if (!chit.AdvanceToSubtree() || !chit.Next(charnum) ||
+ chit.IsAfterSubtree()) {
+ aRv.ThrowIndexSizeError("Character index out of range");
+ return 0;
+ }
+
+ if (nchars == 0) {
+ return 0.0f;
+ }
+
+ charnum = chit.TextElementCharIndex();
+ chit.NextWithinSubtree(nchars);
+ nchars = chit.TextElementCharIndex() - charnum;
+
+ // Find each rendered run that intersects with the range defined
+ // by charnum/nchars.
+ nscoord textLength = 0;
+ TextRenderedRunIterator runIter(this, TextRenderedRunIterator::eAllFrames);
+ TextRenderedRun run = runIter.Current();
+ while (run.mFrame) {
+ // If this rendered run is past the substring we are interested in, we
+ // are done.
+ uint32_t offset = run.mTextElementCharIndex;
+ if (offset >= charnum + nchars) {
+ break;
+ }
+
+ // Intersect the substring we are interested in with the range covered by
+ // the rendered run.
+ uint32_t length = run.mTextFrameContentLength;
+ IntersectInterval(offset, length, charnum, nchars);
+
+ if (length != 0) {
+ // Convert offset into an index into the frame.
+ offset += run.mTextFrameContentOffset - run.mTextElementCharIndex;
+
+ gfxSkipCharsIterator it =
+ run.mFrame->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = run.mFrame->GetTextRun(nsTextFrame::eInflated);
+ nsTextFrame::PropertyProvider provider(run.mFrame, it);
+
+ Range range = ConvertOriginalToSkipped(it, offset, length);
+
+ // Accumulate the advance.
+ textLength += textRun->GetAdvanceWidth(range, &provider);
+ }
+
+ run = runIter.Next();
+ }
+
+ nsPresContext* presContext = PresContext();
+ float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(
+ presContext->AppUnitsPerDevPixel());
+
+ return presContext->AppUnitsToGfxUnits(textLength) * cssPxPerDevPx /
+ mFontSizeScaleFactor;
+}
+
+/**
+ * Implements the SVG DOM GetCharNumAtPosition method for the specified
+ * text content element.
+ */
+int32_t SVGTextFrame::GetCharNumAtPosition(nsIContent* aContent,
+ const DOMPointInit& aPoint) {
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (kid->IsSubtreeDirty()) {
+ // We're never reflowed if we're under a non-SVG element that is
+ // never reflowed (such as the HTML 'caption' element).
+ return -1;
+ }
+
+ UpdateGlyphPositioning();
+
+ nsPresContext* context = PresContext();
+
+ gfxPoint p(aPoint.mX, aPoint.mY);
+
+ int32_t result = -1;
+
+ TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames,
+ aContent);
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ // Hit test this rendered run. Later runs will override earlier ones.
+ int32_t index = run.GetCharNumAtPosition(context, p);
+ if (index != -1) {
+ result = index + run.mTextElementCharIndex;
+ }
+ }
+
+ if (result == -1) {
+ return result;
+ }
+
+ return ConvertTextElementCharIndexToAddressableIndex(result, aContent);
+}
+
+/**
+ * Implements the SVG DOM GetStartPositionOfChar method for the specified
+ * text content element.
+ */
+already_AddRefed<DOMSVGPoint> SVGTextFrame::GetStartPositionOfChar(
+ nsIContent* aContent, uint32_t aCharNum, ErrorResult& aRv) {
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (kid->IsSubtreeDirty()) {
+ // We're never reflowed if we're under a non-SVG element that is
+ // never reflowed (such as the HTML 'caption' element).
+ aRv.ThrowInvalidStateError("No layout information available for SVG text");
+ return nullptr;
+ }
+
+ UpdateGlyphPositioning();
+
+ CharIterator it(this, CharIterator::eAddressable, aContent);
+ if (!it.AdvanceToSubtree() || !it.Next(aCharNum)) {
+ aRv.ThrowIndexSizeError("Character index out of range");
+ return nullptr;
+ }
+
+ // We need to return the start position of the whole glyph.
+ uint32_t startIndex = it.GlyphStartTextElementCharIndex();
+
+ RefPtr<DOMSVGPoint> point =
+ new DOMSVGPoint(ToPoint(mPositions[startIndex].mPosition));
+ return point.forget();
+}
+
+/**
+ * Returns the advance of the entire glyph whose starting character is at
+ * aTextElementCharIndex.
+ *
+ * aIterator, if provided, must be a CharIterator that already points to
+ * aTextElementCharIndex that is restricted to aContent and is using
+ * filter mode eAddressable.
+ */
+static gfxFloat GetGlyphAdvance(SVGTextFrame* aFrame, nsIContent* aContent,
+ uint32_t aTextElementCharIndex,
+ CharIterator* aIterator) {
+ MOZ_ASSERT(!aIterator || (aIterator->Filter() == CharIterator::eAddressable &&
+ aIterator->GetSubtree() == aContent &&
+ aIterator->GlyphStartTextElementCharIndex() ==
+ aTextElementCharIndex),
+ "Invalid aIterator");
+
+ Maybe<CharIterator> newIterator;
+ CharIterator* it = aIterator;
+ if (!it) {
+ newIterator.emplace(aFrame, CharIterator::eAddressable, aContent);
+ if (!newIterator->AdvanceToSubtree()) {
+ MOZ_ASSERT_UNREACHABLE("Invalid aContent");
+ return 0.0;
+ }
+ it = newIterator.ptr();
+ }
+
+ while (it->GlyphStartTextElementCharIndex() != aTextElementCharIndex) {
+ if (!it->Next()) {
+ MOZ_ASSERT_UNREACHABLE("Invalid aTextElementCharIndex");
+ return 0.0;
+ }
+ }
+
+ if (it->AtEnd()) {
+ MOZ_ASSERT_UNREACHABLE("Invalid aTextElementCharIndex");
+ return 0.0;
+ }
+
+ nsPresContext* presContext = aFrame->PresContext();
+ gfxFloat advance = 0.0;
+
+ for (;;) {
+ advance += it->GetAdvance(presContext);
+ if (!it->Next() ||
+ it->GlyphStartTextElementCharIndex() != aTextElementCharIndex) {
+ break;
+ }
+ }
+
+ return advance;
+}
+
+/**
+ * Implements the SVG DOM GetEndPositionOfChar method for the specified
+ * text content element.
+ */
+already_AddRefed<DOMSVGPoint> SVGTextFrame::GetEndPositionOfChar(
+ nsIContent* aContent, uint32_t aCharNum, ErrorResult& aRv) {
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (kid->IsSubtreeDirty()) {
+ // We're never reflowed if we're under a non-SVG element that is
+ // never reflowed (such as the HTML 'caption' element).
+ aRv.ThrowInvalidStateError("No layout information available for SVG text");
+ return nullptr;
+ }
+
+ UpdateGlyphPositioning();
+
+ CharIterator it(this, CharIterator::eAddressable, aContent);
+ if (!it.AdvanceToSubtree() || !it.Next(aCharNum)) {
+ aRv.ThrowIndexSizeError("Character index out of range");
+ return nullptr;
+ }
+
+ // We need to return the end position of the whole glyph.
+ uint32_t startIndex = it.GlyphStartTextElementCharIndex();
+
+ // Get the advance of the glyph.
+ gfxFloat advance =
+ GetGlyphAdvance(this, aContent, startIndex,
+ it.IsClusterAndLigatureGroupStart() ? &it : nullptr);
+ if (it.TextRun()->IsRightToLeft()) {
+ advance = -advance;
+ }
+
+ // The end position is the start position plus the advance in the direction
+ // of the glyph's rotation.
+ Matrix m = Matrix::Rotation(mPositions[startIndex].mAngle) *
+ Matrix::Translation(ToPoint(mPositions[startIndex].mPosition));
+ Point p = m.TransformPoint(Point(advance / mFontSizeScaleFactor, 0));
+
+ RefPtr<DOMSVGPoint> point = new DOMSVGPoint(p);
+ return point.forget();
+}
+
+/**
+ * Implements the SVG DOM GetExtentOfChar method for the specified
+ * text content element.
+ */
+already_AddRefed<SVGRect> SVGTextFrame::GetExtentOfChar(nsIContent* aContent,
+ uint32_t aCharNum,
+ ErrorResult& aRv) {
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (kid->IsSubtreeDirty()) {
+ // We're never reflowed if we're under a non-SVG element that is
+ // never reflowed (such as the HTML 'caption' element).
+ aRv.ThrowInvalidStateError("No layout information available for SVG text");
+ return nullptr;
+ }
+
+ UpdateGlyphPositioning();
+
+ // Search for the character whose addressable index is aCharNum.
+ CharIterator it(this, CharIterator::eAddressable, aContent);
+ if (!it.AdvanceToSubtree() || !it.Next(aCharNum)) {
+ aRv.ThrowIndexSizeError("Character index out of range");
+ return nullptr;
+ }
+
+ nsPresContext* presContext = PresContext();
+ float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(
+ presContext->AppUnitsPerDevPixel());
+
+ nsTextFrame* textFrame = it.TextFrame();
+ uint32_t startIndex = it.GlyphStartTextElementCharIndex();
+ bool isRTL = it.TextRun()->IsRightToLeft();
+ bool isVertical = it.TextRun()->IsVertical();
+
+ // Get the glyph advance.
+ gfxFloat advance =
+ GetGlyphAdvance(this, aContent, startIndex,
+ it.IsClusterAndLigatureGroupStart() ? &it : nullptr);
+ gfxFloat x = isRTL ? -advance : 0.0;
+
+ // The ascent and descent gives the height of the glyph.
+ gfxFloat ascent, descent;
+ GetAscentAndDescentInAppUnits(textFrame, ascent, descent);
+
+ // The horizontal extent is the origin of the glyph plus the advance
+ // in the direction of the glyph's rotation.
+ gfxMatrix m;
+ m.PreTranslate(mPositions[startIndex].mPosition);
+ m.PreRotate(mPositions[startIndex].mAngle);
+ m.PreScale(1 / mFontSizeScaleFactor, 1 / mFontSizeScaleFactor);
+
+ gfxRect glyphRect;
+ if (isVertical) {
+ glyphRect = gfxRect(
+ -presContext->AppUnitsToGfxUnits(descent) * cssPxPerDevPx, x,
+ presContext->AppUnitsToGfxUnits(ascent + descent) * cssPxPerDevPx,
+ advance);
+ } else {
+ glyphRect = gfxRect(
+ x, -presContext->AppUnitsToGfxUnits(ascent) * cssPxPerDevPx, advance,
+ presContext->AppUnitsToGfxUnits(ascent + descent) * cssPxPerDevPx);
+ }
+
+ // Transform the glyph's rect into user space.
+ gfxRect r = m.TransformBounds(glyphRect);
+
+ RefPtr<SVGRect> rect = new SVGRect(aContent, ToRect(r));
+ return rect.forget();
+}
+
+/**
+ * Implements the SVG DOM GetRotationOfChar method for the specified
+ * text content element.
+ */
+float SVGTextFrame::GetRotationOfChar(nsIContent* aContent, uint32_t aCharNum,
+ ErrorResult& aRv) {
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (kid->IsSubtreeDirty()) {
+ // We're never reflowed if we're under a non-SVG element that is
+ // never reflowed (such as the HTML 'caption' element).
+ aRv.ThrowInvalidStateError("No layout information available for SVG text");
+ return 0;
+ }
+
+ UpdateGlyphPositioning();
+
+ CharIterator it(this, CharIterator::eAddressable, aContent);
+ if (!it.AdvanceToSubtree() || !it.Next(aCharNum)) {
+ aRv.ThrowIndexSizeError("Character index out of range");
+ return 0;
+ }
+
+ // we need to account for the glyph's underlying orientation
+ const gfxTextRun::GlyphRun& glyphRun = it.GlyphRun();
+ int32_t glyphOrientation =
+ 90 * (glyphRun.IsSidewaysRight() - glyphRun.IsSidewaysLeft());
+
+ return mPositions[it.TextElementCharIndex()].mAngle * 180.0 / M_PI +
+ glyphOrientation;
+}
+
+//----------------------------------------------------------------------
+// SVGTextFrame text layout methods
+
+/**
+ * Given the character position array before values have been filled in
+ * to any unspecified positions, and an array of dx/dy values, returns whether
+ * a character at a given index should start a new rendered run.
+ *
+ * @param aPositions The array of character positions before unspecified
+ * positions have been filled in and dx/dy values have been added to them.
+ * @param aDeltas The array of dx/dy values.
+ * @param aIndex The character index in question.
+ */
+static bool ShouldStartRunAtIndex(const nsTArray<CharPosition>& aPositions,
+ const nsTArray<gfxPoint>& aDeltas,
+ uint32_t aIndex) {
+ if (aIndex == 0) {
+ return true;
+ }
+
+ if (aIndex < aPositions.Length()) {
+ // If an explicit x or y value was given, start a new run.
+ if (aPositions[aIndex].IsXSpecified() ||
+ aPositions[aIndex].IsYSpecified()) {
+ return true;
+ }
+
+ // If a non-zero rotation was given, or the previous character had a non-
+ // zero rotation, start a new run.
+ if ((aPositions[aIndex].IsAngleSpecified() &&
+ aPositions[aIndex].mAngle != 0.0f) ||
+ (aPositions[aIndex - 1].IsAngleSpecified() &&
+ (aPositions[aIndex - 1].mAngle != 0.0f))) {
+ return true;
+ }
+ }
+
+ if (aIndex < aDeltas.Length()) {
+ // If a non-zero dx or dy value was given, start a new run.
+ if (aDeltas[aIndex].x != 0.0 || aDeltas[aIndex].y != 0.0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool SVGTextFrame::ResolvePositionsForNode(nsIContent* aContent,
+ uint32_t& aIndex, bool aInTextPath,
+ bool& aForceStartOfChunk,
+ nsTArray<gfxPoint>& aDeltas) {
+ if (aContent->IsText()) {
+ // We found a text node.
+ uint32_t length = aContent->AsText()->TextLength();
+ if (length) {
+ uint32_t end = aIndex + length;
+ if (MOZ_UNLIKELY(end > mPositions.Length())) {
+ MOZ_ASSERT_UNREACHABLE(
+ "length of mPositions does not match characters "
+ "found by iterating content");
+ return false;
+ }
+ if (aForceStartOfChunk) {
+ // Note this character as starting a new anchored chunk.
+ mPositions[aIndex].mStartOfChunk = true;
+ aForceStartOfChunk = false;
+ }
+ while (aIndex < end) {
+ // Record whether each of these characters should start a new rendered
+ // run. That is always the case for characters on a text path.
+ //
+ // Run boundaries due to rotate="" values are handled in
+ // DoGlyphPositioning.
+ if (aInTextPath || ShouldStartRunAtIndex(mPositions, aDeltas, aIndex)) {
+ mPositions[aIndex].mRunBoundary = true;
+ }
+ aIndex++;
+ }
+ }
+ return true;
+ }
+
+ // Skip past elements that aren't text content elements.
+ if (!IsTextContentElement(aContent)) {
+ return true;
+ }
+
+ if (aContent->IsSVGElement(nsGkAtoms::textPath)) {
+ // Any ‘y’ attributes on horizontal <textPath> elements are ignored.
+ // Similarly, for vertical <texPath>s x attributes are ignored.
+ // <textPath> elements behave as if they have x="0" y="0" on them, but only
+ // if there is not a value for the non-ignored coordinate that got inherited
+ // from a parent. We skip this if there is no text content, so that empty
+ // <textPath>s don't interrupt the layout of text in the parent element.
+ if (HasTextContent(aContent)) {
+ if (MOZ_UNLIKELY(aIndex >= mPositions.Length())) {
+ MOZ_ASSERT_UNREACHABLE(
+ "length of mPositions does not match characters "
+ "found by iterating content");
+ return false;
+ }
+ bool vertical = GetWritingMode().IsVertical();
+ if (vertical || !mPositions[aIndex].IsXSpecified()) {
+ mPositions[aIndex].mPosition.x = 0.0;
+ }
+ if (!vertical || !mPositions[aIndex].IsYSpecified()) {
+ mPositions[aIndex].mPosition.y = 0.0;
+ }
+ mPositions[aIndex].mStartOfChunk = true;
+ }
+ } else if (!aContent->IsSVGElement(nsGkAtoms::a)) {
+ MOZ_ASSERT(aContent->IsSVGElement());
+
+ // We have a text content element that can have x/y/dx/dy/rotate attributes.
+ SVGElement* element = static_cast<SVGElement*>(aContent);
+
+ // Get x, y, dx, dy.
+ SVGUserUnitList x, y, dx, dy;
+ element->GetAnimatedLengthListValues(&x, &y, &dx, &dy, nullptr);
+
+ // Get rotate.
+ const SVGNumberList* rotate = nullptr;
+ SVGAnimatedNumberList* animatedRotate =
+ element->GetAnimatedNumberList(nsGkAtoms::rotate);
+ if (animatedRotate) {
+ rotate = &animatedRotate->GetAnimValue();
+ }
+
+ bool percentages = false;
+ uint32_t count = GetTextContentLength(aContent);
+
+ if (MOZ_UNLIKELY(aIndex + count > mPositions.Length())) {
+ MOZ_ASSERT_UNREACHABLE(
+ "length of mPositions does not match characters "
+ "found by iterating content");
+ return false;
+ }
+
+ // New text anchoring chunks start at each character assigned a position
+ // with x="" or y="", or if we forced one with aForceStartOfChunk due to
+ // being just after a <textPath>.
+ uint32_t newChunkCount = std::max(x.Length(), y.Length());
+ if (!newChunkCount && aForceStartOfChunk) {
+ newChunkCount = 1;
+ }
+ for (uint32_t i = 0, j = 0; i < newChunkCount && j < count; j++) {
+ if (!mPositions[aIndex + j].mUnaddressable) {
+ mPositions[aIndex + j].mStartOfChunk = true;
+ i++;
+ }
+ }
+
+ // Copy dx="" and dy="" values into aDeltas.
+ if (!dx.IsEmpty() || !dy.IsEmpty()) {
+ // Any unspecified deltas when we grow the array just get left as 0s.
+ aDeltas.EnsureLengthAtLeast(aIndex + count);
+ for (uint32_t i = 0, j = 0; i < dx.Length() && j < count; j++) {
+ if (!mPositions[aIndex + j].mUnaddressable) {
+ aDeltas[aIndex + j].x = dx[i];
+ percentages = percentages || dx.HasPercentageValueAt(i);
+ i++;
+ }
+ }
+ for (uint32_t i = 0, j = 0; i < dy.Length() && j < count; j++) {
+ if (!mPositions[aIndex + j].mUnaddressable) {
+ aDeltas[aIndex + j].y = dy[i];
+ percentages = percentages || dy.HasPercentageValueAt(i);
+ i++;
+ }
+ }
+ }
+
+ // Copy x="" and y="" values.
+ for (uint32_t i = 0, j = 0; i < x.Length() && j < count; j++) {
+ if (!mPositions[aIndex + j].mUnaddressable) {
+ mPositions[aIndex + j].mPosition.x = x[i];
+ percentages = percentages || x.HasPercentageValueAt(i);
+ i++;
+ }
+ }
+ for (uint32_t i = 0, j = 0; i < y.Length() && j < count; j++) {
+ if (!mPositions[aIndex + j].mUnaddressable) {
+ mPositions[aIndex + j].mPosition.y = y[i];
+ percentages = percentages || y.HasPercentageValueAt(i);
+ i++;
+ }
+ }
+
+ // Copy rotate="" values.
+ if (rotate && !rotate->IsEmpty()) {
+ uint32_t i = 0, j = 0;
+ while (i < rotate->Length() && j < count) {
+ if (!mPositions[aIndex + j].mUnaddressable) {
+ mPositions[aIndex + j].mAngle = M_PI * (*rotate)[i] / 180.0;
+ i++;
+ }
+ j++;
+ }
+ // Propagate final rotate="" value to the end of this element.
+ while (j < count) {
+ mPositions[aIndex + j].mAngle = mPositions[aIndex + j - 1].mAngle;
+ j++;
+ }
+ }
+
+ if (percentages) {
+ AddStateBits(NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES);
+ }
+ }
+
+ // Recurse to children.
+ bool inTextPath = aInTextPath || aContent->IsSVGElement(nsGkAtoms::textPath);
+ for (nsIContent* child = aContent->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ bool ok = ResolvePositionsForNode(child, aIndex, inTextPath,
+ aForceStartOfChunk, aDeltas);
+ if (!ok) {
+ return false;
+ }
+ }
+
+ if (aContent->IsSVGElement(nsGkAtoms::textPath)) {
+ // Force a new anchored chunk just after a <textPath>.
+ aForceStartOfChunk = true;
+ }
+
+ return true;
+}
+
+bool SVGTextFrame::ResolvePositions(nsTArray<gfxPoint>& aDeltas,
+ bool aRunPerGlyph) {
+ NS_ASSERTION(mPositions.IsEmpty(), "expected mPositions to be empty");
+ RemoveStateBits(NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES);
+
+ CharIterator it(this, CharIterator::eOriginal, /* aSubtree */ nullptr);
+ if (it.AtEnd()) {
+ return false;
+ }
+
+ // We assume the first character position is (0,0) unless we later see
+ // otherwise, and note it as unaddressable if it is.
+ bool firstCharUnaddressable = it.IsOriginalCharUnaddressable();
+ mPositions.AppendElement(CharPosition::Unspecified(firstCharUnaddressable));
+
+ // Fill in unspecified positions for all remaining characters, noting
+ // them as unaddressable if they are.
+ uint32_t index = 0;
+ while (it.Next()) {
+ while (++index < it.TextElementCharIndex()) {
+ mPositions.AppendElement(CharPosition::Unspecified(false));
+ }
+ mPositions.AppendElement(
+ CharPosition::Unspecified(it.IsOriginalCharUnaddressable()));
+ }
+ while (++index < it.TextElementCharIndex()) {
+ mPositions.AppendElement(CharPosition::Unspecified(false));
+ }
+
+ // Recurse over the content and fill in character positions as we go.
+ bool forceStartOfChunk = false;
+ index = 0;
+ bool ok = ResolvePositionsForNode(mContent, index, aRunPerGlyph,
+ forceStartOfChunk, aDeltas);
+ return ok && index > 0;
+}
+
+void SVGTextFrame::DetermineCharPositions(nsTArray<nsPoint>& aPositions) {
+ NS_ASSERTION(aPositions.IsEmpty(), "expected aPositions to be empty");
+
+ nsPoint position;
+
+ TextFrameIterator frit(this);
+ for (nsTextFrame* frame = frit.Current(); frame; frame = frit.Next()) {
+ gfxSkipCharsIterator it = frame->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = frame->GetTextRun(nsTextFrame::eInflated);
+ nsTextFrame::PropertyProvider provider(frame, it);
+
+ // Reset the position to the new frame's position.
+ position = frit.Position();
+ if (textRun->IsVertical()) {
+ if (textRun->IsRightToLeft()) {
+ position.y += frame->GetRect().height;
+ }
+ position.x += GetBaselinePosition(frame, textRun, frit.DominantBaseline(),
+ mFontSizeScaleFactor);
+ } else {
+ if (textRun->IsRightToLeft()) {
+ position.x += frame->GetRect().width;
+ }
+ position.y += GetBaselinePosition(frame, textRun, frit.DominantBaseline(),
+ mFontSizeScaleFactor);
+ }
+
+ // Any characters not in a frame, e.g. when display:none.
+ for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) {
+ aPositions.AppendElement(position);
+ }
+
+ // Any white space characters trimmed at the start of the line of text.
+ nsTextFrame::TrimmedOffsets trimmedOffsets =
+ frame->GetTrimmedOffsets(frame->TextFragment());
+ while (it.GetOriginalOffset() < trimmedOffsets.mStart) {
+ aPositions.AppendElement(position);
+ it.AdvanceOriginal(1);
+ }
+
+ // Visible characters in the text frame.
+ while (it.GetOriginalOffset() < frame->GetContentEnd()) {
+ aPositions.AppendElement(position);
+ if (!it.IsOriginalCharSkipped()) {
+ // Accumulate partial ligature advance into position. (We must get
+ // partial advances rather than get the advance of the whole ligature
+ // group / cluster at once, since the group may span text frames, and
+ // the PropertyProvider only has spacing information for the current
+ // text frame.)
+ uint32_t offset = it.GetSkippedOffset();
+ nscoord advance =
+ textRun->GetAdvanceWidth(Range(offset, offset + 1), &provider);
+ (textRun->IsVertical() ? position.y : position.x) +=
+ textRun->IsRightToLeft() ? -advance : advance;
+ }
+ it.AdvanceOriginal(1);
+ }
+ }
+
+ // Finally any characters at the end that are not in a frame.
+ for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) {
+ aPositions.AppendElement(position);
+ }
+}
+
+/**
+ * Physical text-anchor values.
+ */
+enum TextAnchorSide { eAnchorLeft, eAnchorMiddle, eAnchorRight };
+
+/**
+ * Converts a logical text-anchor value to its physical value, based on whether
+ * it is for an RTL frame.
+ */
+static TextAnchorSide ConvertLogicalTextAnchorToPhysical(
+ StyleTextAnchor aTextAnchor, bool aIsRightToLeft) {
+ NS_ASSERTION(uint8_t(aTextAnchor) <= 3, "unexpected value for aTextAnchor");
+ if (!aIsRightToLeft) {
+ return TextAnchorSide(uint8_t(aTextAnchor));
+ }
+ return TextAnchorSide(2 - uint8_t(aTextAnchor));
+}
+
+/**
+ * Shifts the recorded character positions for an anchored chunk.
+ *
+ * @param aCharPositions The recorded character positions.
+ * @param aChunkStart The character index the starts the anchored chunk. This
+ * character's initial position is the anchor point.
+ * @param aChunkEnd The character index just after the end of the anchored
+ * chunk.
+ * @param aVisIStartEdge The left/top-most edge of any of the glyphs within the
+ * anchored chunk.
+ * @param aVisIEndEdge The right/bottom-most edge of any of the glyphs within
+ * the anchored chunk.
+ * @param aAnchorSide The direction to anchor.
+ */
+static void ShiftAnchoredChunk(nsTArray<CharPosition>& aCharPositions,
+ uint32_t aChunkStart, uint32_t aChunkEnd,
+ gfxFloat aVisIStartEdge, gfxFloat aVisIEndEdge,
+ TextAnchorSide aAnchorSide, bool aVertical) {
+ NS_ASSERTION(aVisIStartEdge <= aVisIEndEdge,
+ "unexpected anchored chunk edges");
+ NS_ASSERTION(aChunkStart < aChunkEnd,
+ "unexpected values for aChunkStart and aChunkEnd");
+
+ gfxFloat shift = aVertical ? aCharPositions[aChunkStart].mPosition.y
+ : aCharPositions[aChunkStart].mPosition.x;
+ switch (aAnchorSide) {
+ case eAnchorLeft:
+ shift -= aVisIStartEdge;
+ break;
+ case eAnchorMiddle:
+ shift -= (aVisIStartEdge + aVisIEndEdge) / 2;
+ break;
+ case eAnchorRight:
+ shift -= aVisIEndEdge;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected value for aAnchorSide");
+ }
+
+ if (shift != 0.0) {
+ if (aVertical) {
+ for (uint32_t i = aChunkStart; i < aChunkEnd; i++) {
+ aCharPositions[i].mPosition.y += shift;
+ }
+ } else {
+ for (uint32_t i = aChunkStart; i < aChunkEnd; i++) {
+ aCharPositions[i].mPosition.x += shift;
+ }
+ }
+ }
+}
+
+void SVGTextFrame::AdjustChunksForLineBreaks() {
+ nsBlockFrame* block = do_QueryFrame(PrincipalChildList().FirstChild());
+ NS_ASSERTION(block, "expected block frame");
+
+ nsBlockFrame::LineIterator line = block->LinesBegin();
+
+ CharIterator it(this, CharIterator::eOriginal, /* aSubtree */ nullptr);
+ while (!it.AtEnd() && line != block->LinesEnd()) {
+ if (it.TextFrame() == line->mFirstChild) {
+ mPositions[it.TextElementCharIndex()].mStartOfChunk = true;
+ line++;
+ }
+ it.AdvancePastCurrentFrame();
+ }
+}
+
+void SVGTextFrame::AdjustPositionsForClusters() {
+ nsPresContext* presContext = PresContext();
+
+ // Find all of the characters that are in the middle of a cluster or
+ // ligature group, and adjust their positions and rotations to match
+ // the first character of the cluster/group.
+ //
+ // Also move the boundaries of text rendered runs and anchored chunks to
+ // not lie in the middle of cluster/group.
+
+ // The partial advance of the current cluster or ligature group that we
+ // have accumulated.
+ gfxFloat partialAdvance = 0.0;
+
+ CharIterator it(this, CharIterator::eUnskipped, /* aSubtree */ nullptr);
+ while (!it.AtEnd()) {
+ if (it.IsClusterAndLigatureGroupStart()) {
+ // If we're at the start of a new cluster or ligature group, reset our
+ // accumulated partial advance.
+ partialAdvance = 0.0;
+ } else {
+ // Otherwise, we're in the middle of a cluster or ligature group, and
+ // we need to use the currently accumulated partial advance to adjust
+ // the character's position and rotation.
+
+ // Find the start of the cluster/ligature group.
+ uint32_t charIndex = it.TextElementCharIndex();
+ uint32_t startIndex = it.GlyphStartTextElementCharIndex();
+ MOZ_ASSERT(charIndex != startIndex,
+ "If the current character is in the middle of a cluster or "
+ "ligature group, then charIndex must be different from "
+ "startIndex");
+
+ mPositions[charIndex].mClusterOrLigatureGroupMiddle = true;
+
+ // Don't allow different rotations on ligature parts.
+ bool rotationAdjusted = false;
+ double angle = mPositions[startIndex].mAngle;
+ if (mPositions[charIndex].mAngle != angle) {
+ mPositions[charIndex].mAngle = angle;
+ rotationAdjusted = true;
+ }
+
+ // Update the character position.
+ gfxFloat advance = partialAdvance / mFontSizeScaleFactor;
+ gfxPoint direction = gfxPoint(cos(angle), sin(angle)) *
+ (it.TextRun()->IsRightToLeft() ? -1.0 : 1.0);
+ if (it.TextRun()->IsVertical()) {
+ std::swap(direction.x, direction.y);
+ }
+ mPositions[charIndex].mPosition =
+ mPositions[startIndex].mPosition + direction * advance;
+
+ // Ensure any runs that would end in the middle of a ligature now end just
+ // after the ligature.
+ if (mPositions[charIndex].mRunBoundary) {
+ mPositions[charIndex].mRunBoundary = false;
+ if (charIndex + 1 < mPositions.Length()) {
+ mPositions[charIndex + 1].mRunBoundary = true;
+ }
+ } else if (rotationAdjusted) {
+ if (charIndex + 1 < mPositions.Length()) {
+ mPositions[charIndex + 1].mRunBoundary = true;
+ }
+ }
+
+ // Ensure any anchored chunks that would begin in the middle of a ligature
+ // now begin just after the ligature.
+ if (mPositions[charIndex].mStartOfChunk) {
+ mPositions[charIndex].mStartOfChunk = false;
+ if (charIndex + 1 < mPositions.Length()) {
+ mPositions[charIndex + 1].mStartOfChunk = true;
+ }
+ }
+ }
+
+ // Accumulate the current character's partial advance.
+ partialAdvance += it.GetAdvance(presContext);
+
+ it.Next();
+ }
+}
+
+already_AddRefed<Path> SVGTextFrame::GetTextPath(nsIFrame* aTextPathFrame) {
+ nsIContent* content = aTextPathFrame->GetContent();
+ SVGTextPathElement* tp = static_cast<SVGTextPathElement*>(content);
+ if (tp->mPath.IsRendered()) {
+ // This is just an attribute so there's no transform that can apply
+ // so we can just return the path directly.
+ return tp->mPath.GetAnimValue().BuildPathForMeasuring();
+ }
+
+ SVGGeometryElement* geomElement =
+ SVGObserverUtils::GetAndObserveTextPathsPath(aTextPathFrame);
+ if (!geomElement) {
+ return nullptr;
+ }
+
+ RefPtr<Path> path = geomElement->GetOrBuildPathForMeasuring();
+ if (!path) {
+ return nullptr;
+ }
+
+ gfxMatrix matrix = geomElement->PrependLocalTransformsTo(gfxMatrix());
+ if (!matrix.IsIdentity()) {
+ // Apply the geometry element's transform
+ RefPtr<PathBuilder> builder =
+ path->TransformedCopyToBuilder(ToMatrix(matrix));
+ path = builder->Finish();
+ }
+
+ return path.forget();
+}
+
+gfxFloat SVGTextFrame::GetOffsetScale(nsIFrame* aTextPathFrame) {
+ nsIContent* content = aTextPathFrame->GetContent();
+ SVGTextPathElement* tp = static_cast<SVGTextPathElement*>(content);
+ if (tp->mPath.IsRendered()) {
+ // A path attribute has no pathLength or transform
+ // so we return a unit scale.
+ return 1.0;
+ }
+
+ SVGGeometryElement* geomElement =
+ SVGObserverUtils::GetAndObserveTextPathsPath(aTextPathFrame);
+ if (!geomElement) {
+ return 1.0;
+ }
+ return geomElement->GetPathLengthScale(SVGGeometryElement::eForTextPath);
+}
+
+gfxFloat SVGTextFrame::GetStartOffset(nsIFrame* aTextPathFrame) {
+ SVGTextPathElement* tp =
+ static_cast<SVGTextPathElement*>(aTextPathFrame->GetContent());
+ SVGAnimatedLength* length =
+ &tp->mLengthAttributes[SVGTextPathElement::STARTOFFSET];
+
+ if (length->IsPercentage()) {
+ RefPtr<Path> data = GetTextPath(aTextPathFrame);
+ return data ? length->GetAnimValInSpecifiedUnits() * data->ComputeLength() /
+ 100.0
+ : 0.0;
+ }
+ return length->GetAnimValue(tp) * GetOffsetScale(aTextPathFrame);
+}
+
+void SVGTextFrame::DoTextPathLayout() {
+ nsPresContext* context = PresContext();
+
+ CharIterator it(this, CharIterator::eOriginal, /* aSubtree */ nullptr);
+ while (!it.AtEnd()) {
+ nsIFrame* textPathFrame = it.TextPathFrame();
+ if (!textPathFrame) {
+ // Skip past this frame if we're not in a text path.
+ it.AdvancePastCurrentFrame();
+ continue;
+ }
+
+ // Get the path itself.
+ RefPtr<Path> path = GetTextPath(textPathFrame);
+ if (!path) {
+ uint32_t start = it.TextElementCharIndex();
+ it.AdvancePastCurrentTextPathFrame();
+ uint32_t end = it.TextElementCharIndex();
+ for (uint32_t i = start; i < end; i++) {
+ mPositions[i].mHidden = true;
+ }
+ continue;
+ }
+
+ SVGTextPathElement* textPath =
+ static_cast<SVGTextPathElement*>(textPathFrame->GetContent());
+ uint16_t side =
+ textPath->EnumAttributes()[SVGTextPathElement::SIDE].GetAnimValue();
+
+ gfxFloat offset = GetStartOffset(textPathFrame);
+ Float pathLength = path->ComputeLength();
+
+ // If the first character within the text path is in the middle of a
+ // cluster or ligature group, just skip it and don't apply text path
+ // positioning.
+ while (!it.AtEnd()) {
+ if (it.IsOriginalCharSkipped()) {
+ it.Next();
+ continue;
+ }
+ if (it.IsClusterAndLigatureGroupStart()) {
+ break;
+ }
+ it.Next();
+ }
+
+ bool skippedEndOfTextPath = false;
+
+ // Loop for each character in the text path.
+ while (!it.AtEnd() && it.TextPathFrame() &&
+ it.TextPathFrame()->GetContent() == textPath) {
+ // The index of the cluster or ligature group's first character.
+ uint32_t i = it.TextElementCharIndex();
+
+ // The index of the next character of the cluster or ligature.
+ // We track this as we loop over the characters below so that we
+ // can detect undisplayed characters and append entries into
+ // partialAdvances for them.
+ uint32_t j = i + 1;
+
+ MOZ_ASSERT(!mPositions[i].mClusterOrLigatureGroupMiddle);
+
+ gfxFloat sign = it.TextRun()->IsRightToLeft() ? -1.0 : 1.0;
+ bool vertical = it.TextRun()->IsVertical();
+
+ // Compute cumulative advances for each character of the cluster or
+ // ligature group.
+ AutoTArray<gfxFloat, 4> partialAdvances;
+ gfxFloat partialAdvance = it.GetAdvance(context);
+ partialAdvances.AppendElement(partialAdvance);
+ while (it.Next()) {
+ // Append entries for any undisplayed characters the CharIterator
+ // skipped over.
+ MOZ_ASSERT(j <= it.TextElementCharIndex());
+ while (j < it.TextElementCharIndex()) {
+ partialAdvances.AppendElement(partialAdvance);
+ ++j;
+ }
+ // This loop may end up outside of the current text path, but
+ // that's OK; we'll consider any complete cluster or ligature
+ // group that begins inside the text path as being affected
+ // by it.
+ if (it.IsOriginalCharSkipped()) {
+ if (!it.TextPathFrame()) {
+ skippedEndOfTextPath = true;
+ break;
+ }
+ // Leave partialAdvance unchanged.
+ } else if (it.IsClusterAndLigatureGroupStart()) {
+ break;
+ } else {
+ partialAdvance += it.GetAdvance(context);
+ }
+ partialAdvances.AppendElement(partialAdvance);
+ }
+ if (skippedEndOfTextPath) {
+ break;
+ }
+
+ // Any final undisplayed characters the CharIterator skipped over.
+ MOZ_ASSERT(j <= it.TextElementCharIndex());
+ while (j < it.TextElementCharIndex()) {
+ partialAdvances.AppendElement(partialAdvance);
+ ++j;
+ }
+
+ gfxFloat halfAdvance =
+ partialAdvances.LastElement() / mFontSizeScaleFactor / 2.0;
+ gfxFloat midx =
+ (vertical ? mPositions[i].mPosition.y : mPositions[i].mPosition.x) +
+ sign * halfAdvance + offset;
+
+ // Hide the character if it falls off the end of the path.
+ mPositions[i].mHidden = midx < 0 || midx > pathLength;
+
+ // Position the character on the path at the right angle.
+ Point tangent; // Unit vector tangent to the point we find.
+ Point pt;
+ if (side == TEXTPATH_SIDETYPE_RIGHT) {
+ pt = path->ComputePointAtLength(Float(pathLength - midx), &tangent);
+ tangent = -tangent;
+ } else {
+ pt = path->ComputePointAtLength(Float(midx), &tangent);
+ }
+ Float rotation = vertical ? atan2f(-tangent.x, tangent.y)
+ : atan2f(tangent.y, tangent.x);
+ Point normal(-tangent.y, tangent.x); // Unit vector normal to the point.
+ Point offsetFromPath = normal * (vertical ? -mPositions[i].mPosition.x
+ : mPositions[i].mPosition.y);
+ pt += offsetFromPath;
+ Point direction = tangent * sign;
+ mPositions[i].mPosition =
+ ThebesPoint(pt) - ThebesPoint(direction) * halfAdvance;
+ mPositions[i].mAngle += rotation;
+
+ // Position any characters for a partial ligature.
+ for (uint32_t k = i + 1; k < j; k++) {
+ gfxPoint partialAdvance = ThebesPoint(direction) *
+ partialAdvances[k - i] / mFontSizeScaleFactor;
+ mPositions[k].mPosition = mPositions[i].mPosition + partialAdvance;
+ mPositions[k].mAngle = mPositions[i].mAngle;
+ mPositions[k].mHidden = mPositions[i].mHidden;
+ }
+ }
+ }
+}
+
+void SVGTextFrame::DoAnchoring() {
+ nsPresContext* presContext = PresContext();
+
+ CharIterator it(this, CharIterator::eOriginal, /* aSubtree */ nullptr);
+
+ // Don't need to worry about skipped or trimmed characters.
+ while (!it.AtEnd() &&
+ (it.IsOriginalCharSkipped() || it.IsOriginalCharTrimmed())) {
+ it.Next();
+ }
+
+ bool vertical = GetWritingMode().IsVertical();
+ uint32_t start = it.TextElementCharIndex();
+ while (start < mPositions.Length()) {
+ it.AdvanceToCharacter(start);
+ nsTextFrame* chunkFrame = it.TextFrame();
+
+ // Measure characters in this chunk to find the left-most and right-most
+ // edges of all glyphs within the chunk.
+ uint32_t index = it.TextElementCharIndex();
+ uint32_t end = start;
+ gfxFloat left = std::numeric_limits<gfxFloat>::infinity();
+ gfxFloat right = -std::numeric_limits<gfxFloat>::infinity();
+ do {
+ if (!it.IsOriginalCharSkipped() && !it.IsOriginalCharTrimmed()) {
+ gfxFloat advance = it.GetAdvance(presContext) / mFontSizeScaleFactor;
+ gfxFloat pos = it.TextRun()->IsVertical()
+ ? mPositions[index].mPosition.y
+ : mPositions[index].mPosition.x;
+ if (it.TextRun()->IsRightToLeft()) {
+ left = std::min(left, pos - advance);
+ right = std::max(right, pos);
+ } else {
+ left = std::min(left, pos);
+ right = std::max(right, pos + advance);
+ }
+ }
+ it.Next();
+ index = end = it.TextElementCharIndex();
+ } while (!it.AtEnd() && !mPositions[end].mStartOfChunk);
+
+ if (left != std::numeric_limits<gfxFloat>::infinity()) {
+ bool isRTL =
+ chunkFrame->StyleVisibility()->mDirection == StyleDirection::Rtl;
+ TextAnchorSide anchor = ConvertLogicalTextAnchorToPhysical(
+ chunkFrame->StyleSVG()->mTextAnchor, isRTL);
+
+ ShiftAnchoredChunk(mPositions, start, end, left, right, anchor, vertical);
+ }
+
+ start = it.TextElementCharIndex();
+ }
+}
+
+void SVGTextFrame::DoGlyphPositioning() {
+ mPositions.Clear();
+ RemoveStateBits(NS_STATE_SVG_POSITIONING_DIRTY);
+
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (kid && kid->IsSubtreeDirty()) {
+ MOZ_ASSERT(false, "should have already reflowed the kid");
+ return;
+ }
+
+ // Since we can be called directly via GetBBoxContribution, our correspondence
+ // may not be up to date.
+ TextNodeCorrespondenceRecorder::RecordCorrespondence(this);
+
+ // Determine the positions of each character in app units.
+ nsTArray<nsPoint> charPositions;
+ DetermineCharPositions(charPositions);
+
+ if (charPositions.IsEmpty()) {
+ // No characters, so nothing to do.
+ return;
+ }
+
+ // If the textLength="" attribute was specified, then we need ResolvePositions
+ // to record that a new run starts with each glyph.
+ SVGTextContentElement* element =
+ static_cast<SVGTextContentElement*>(GetContent());
+ SVGAnimatedLength* textLengthAttr =
+ element->GetAnimatedLength(nsGkAtoms::textLength);
+ uint16_t lengthAdjust =
+ element->EnumAttributes()[SVGTextContentElement::LENGTHADJUST]
+ .GetAnimValue();
+ bool adjustingTextLength = textLengthAttr->IsExplicitlySet();
+ float expectedTextLength = textLengthAttr->GetAnimValue(element);
+
+ if (adjustingTextLength &&
+ (expectedTextLength < 0.0f || lengthAdjust == LENGTHADJUST_UNKNOWN)) {
+ // If textLength="" is less than zero or lengthAdjust is unknown, ignore it.
+ adjustingTextLength = false;
+ }
+
+ // Get the x, y, dx, dy, rotate values for the subtree.
+ nsTArray<gfxPoint> deltas;
+ if (!ResolvePositions(deltas, adjustingTextLength)) {
+ // If ResolvePositions returned false, it means either there were some
+ // characters in the DOM but none of them are displayed, or there was
+ // an error in processing mPositions. Clear out mPositions so that we don't
+ // attempt to do any painting later.
+ mPositions.Clear();
+ return;
+ }
+
+ // XXX We might be able to do less work when there is at most a single
+ // x/y/dx/dy position.
+
+ // Truncate the positioning arrays to the actual number of characters present.
+ TruncateTo(deltas, charPositions);
+ TruncateTo(mPositions, charPositions);
+
+ // Fill in an unspecified character position at index 0.
+ if (!mPositions[0].IsXSpecified()) {
+ mPositions[0].mPosition.x = 0.0;
+ }
+ if (!mPositions[0].IsYSpecified()) {
+ mPositions[0].mPosition.y = 0.0;
+ }
+ if (!mPositions[0].IsAngleSpecified()) {
+ mPositions[0].mAngle = 0.0;
+ }
+
+ nsPresContext* presContext = PresContext();
+ bool vertical = GetWritingMode().IsVertical();
+
+ float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(
+ presContext->AppUnitsPerDevPixel());
+ double factor = cssPxPerDevPx / mFontSizeScaleFactor;
+
+ // Determine how much to compress or expand glyph positions due to
+ // textLength="" and lengthAdjust="".
+ double adjustment = 0.0;
+ mLengthAdjustScaleFactor = 1.0f;
+ if (adjustingTextLength) {
+ nscoord frameLength =
+ vertical ? PrincipalChildList().FirstChild()->GetRect().height
+ : PrincipalChildList().FirstChild()->GetRect().width;
+ float actualTextLength = static_cast<float>(
+ presContext->AppUnitsToGfxUnits(frameLength) * factor);
+
+ switch (lengthAdjust) {
+ case LENGTHADJUST_SPACINGANDGLYPHS:
+ // Scale the glyphs and their positions.
+ if (actualTextLength > 0) {
+ mLengthAdjustScaleFactor = expectedTextLength / actualTextLength;
+ }
+ break;
+
+ default:
+ MOZ_ASSERT(lengthAdjust == LENGTHADJUST_SPACING);
+ // Just add space between each glyph.
+ int32_t adjustableSpaces = 0;
+ for (uint32_t i = 1; i < mPositions.Length(); i++) {
+ if (!mPositions[i].mUnaddressable) {
+ adjustableSpaces++;
+ }
+ }
+ if (adjustableSpaces) {
+ adjustment =
+ (expectedTextLength - actualTextLength) / adjustableSpaces;
+ }
+ break;
+ }
+ }
+
+ // Fill in any unspecified character positions based on the positions recorded
+ // in charPositions, and also add in the dx/dy values.
+ if (!deltas.IsEmpty()) {
+ mPositions[0].mPosition += deltas[0];
+ }
+
+ gfxFloat xLengthAdjustFactor = vertical ? 1.0 : mLengthAdjustScaleFactor;
+ gfxFloat yLengthAdjustFactor = vertical ? mLengthAdjustScaleFactor : 1.0;
+ for (uint32_t i = 1; i < mPositions.Length(); i++) {
+ // Fill in unspecified x position.
+ if (!mPositions[i].IsXSpecified()) {
+ nscoord d = charPositions[i].x - charPositions[i - 1].x;
+ mPositions[i].mPosition.x =
+ mPositions[i - 1].mPosition.x +
+ presContext->AppUnitsToGfxUnits(d) * factor * xLengthAdjustFactor;
+ if (!vertical && !mPositions[i].mUnaddressable) {
+ mPositions[i].mPosition.x += adjustment;
+ }
+ }
+ // Fill in unspecified y position.
+ if (!mPositions[i].IsYSpecified()) {
+ nscoord d = charPositions[i].y - charPositions[i - 1].y;
+ mPositions[i].mPosition.y =
+ mPositions[i - 1].mPosition.y +
+ presContext->AppUnitsToGfxUnits(d) * factor * yLengthAdjustFactor;
+ if (vertical && !mPositions[i].mUnaddressable) {
+ mPositions[i].mPosition.y += adjustment;
+ }
+ }
+ // Add in dx/dy.
+ if (i < deltas.Length()) {
+ mPositions[i].mPosition += deltas[i];
+ }
+ // Fill in unspecified rotation values.
+ if (!mPositions[i].IsAngleSpecified()) {
+ mPositions[i].mAngle = 0.0f;
+ }
+ }
+
+ MOZ_ASSERT(mPositions.Length() == charPositions.Length());
+
+ AdjustChunksForLineBreaks();
+ AdjustPositionsForClusters();
+ DoAnchoring();
+ DoTextPathLayout();
+}
+
+bool SVGTextFrame::ShouldRenderAsPath(nsTextFrame* aFrame,
+ bool& aShouldPaintSVGGlyphs) {
+ // Rendering to a clip path.
+ if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) {
+ aShouldPaintSVGGlyphs = false;
+ return true;
+ }
+
+ aShouldPaintSVGGlyphs = true;
+
+ const nsStyleSVG* style = aFrame->StyleSVG();
+
+ // Fill is a non-solid paint, has a non-default fill-rule or has
+ // non-1 opacity.
+ if (!(style->mFill.kind.IsNone() ||
+ (style->mFill.kind.IsColor() && style->mFillOpacity.IsOpacity() &&
+ style->mFillOpacity.AsOpacity() == 1))) {
+ return true;
+ }
+
+ // Text has a stroke.
+ if (style->HasStroke()) {
+ if (style->mStrokeWidth.IsContextValue()) {
+ return true;
+ }
+ if (SVGContentUtils::CoordToFloat(
+ static_cast<SVGElement*>(GetContent()),
+ style->mStrokeWidth.AsLengthPercentage()) > 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void SVGTextFrame::ScheduleReflowSVG() {
+ if (mState & NS_FRAME_IS_NONDISPLAY) {
+ ScheduleReflowSVGNonDisplayText(
+ IntrinsicDirty::FrameAncestorsAndDescendants);
+ } else {
+ SVGUtils::ScheduleReflowSVG(this);
+ }
+}
+
+void SVGTextFrame::NotifyGlyphMetricsChange() {
+ // TODO: perf - adding NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY is overly
+ // aggressive here. Ideally we would only set that bit when our descendant
+ // frame tree changes (i.e. after frame construction).
+ AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY |
+ NS_STATE_SVG_POSITIONING_DIRTY);
+ nsLayoutUtils::PostRestyleEvent(mContent->AsElement(), RestyleHint{0},
+ nsChangeHint_InvalidateRenderingObservers);
+ ScheduleReflowSVG();
+}
+
+void SVGTextFrame::UpdateGlyphPositioning() {
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (!kid) {
+ return;
+ }
+
+ if (mState & NS_STATE_SVG_POSITIONING_DIRTY) {
+ DoGlyphPositioning();
+ }
+}
+
+void SVGTextFrame::MaybeResolveBidiForAnonymousBlockChild() {
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+
+ if (kid && kid->HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
+ PresContext()->BidiEnabled()) {
+ MOZ_ASSERT(static_cast<nsBlockFrame*>(do_QueryFrame(kid)),
+ "Expect anonymous child to be an nsBlockFrame");
+ nsBidiPresUtils::Resolve(static_cast<nsBlockFrame*>(kid));
+ }
+}
+
+void SVGTextFrame::MaybeReflowAnonymousBlockChild() {
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (!kid) {
+ return;
+ }
+
+ NS_ASSERTION(!kid->HasAnyStateBits(NS_FRAME_IN_REFLOW),
+ "should not be in reflow when about to reflow again");
+
+ if (IsSubtreeDirty()) {
+ if (mState & NS_FRAME_IS_DIRTY) {
+ // If we require a full reflow, ensure our kid is marked fully dirty.
+ // (Note that our anonymous nsBlockFrame is not an ISVGDisplayableFrame,
+ // so even when we are called via our ReflowSVG this will not be done for
+ // us by SVGDisplayContainerFrame::ReflowSVG.)
+ kid->MarkSubtreeDirty();
+ }
+
+ // The RecordCorrespondence and DoReflow calls can result in new text frames
+ // being created (due to bidi resolution or reflow). We set this bit to
+ // guard against unnecessarily calling back in to
+ // ScheduleReflowSVGNonDisplayText from nsIFrame::DidSetComputedStyle on
+ // those new text frames.
+ AddStateBits(NS_STATE_SVG_TEXT_IN_REFLOW);
+
+ TextNodeCorrespondenceRecorder::RecordCorrespondence(this);
+
+ MOZ_ASSERT(SVGUtils::AnyOuterSVGIsCallingReflowSVG(this),
+ "should be under ReflowSVG");
+ nsPresContext::InterruptPreventer noInterrupts(PresContext());
+ DoReflow();
+
+ RemoveStateBits(NS_STATE_SVG_TEXT_IN_REFLOW);
+ }
+}
+
+void SVGTextFrame::DoReflow() {
+ MOZ_ASSERT(HasAnyStateBits(NS_STATE_SVG_TEXT_IN_REFLOW));
+
+ // Since we are going to reflow the anonymous block frame, we will
+ // need to update mPositions.
+ // We also mark our text correspondence as dirty since we can end up needing
+ // reflow in ways that do not set NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY.
+ // (We'd then fail the "expected a TextNodeCorrespondenceProperty" assertion
+ // when UpdateGlyphPositioning() is called after we return.)
+ AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY |
+ NS_STATE_SVG_POSITIONING_DIRTY);
+
+ if (mState & NS_FRAME_IS_NONDISPLAY) {
+ // Normally, these dirty flags would be cleared in ReflowSVG(), but that
+ // doesn't get called for non-display frames. We don't want to reflow our
+ // descendants every time SVGTextFrame::PaintSVG makes sure that we have
+ // valid positions by calling UpdateGlyphPositioning(), so we need to clear
+ // these dirty bits. Note that this also breaks an invalidation loop where
+ // our descendants invalidate as they reflow, which invalidates rendering
+ // observers, which reschedules the frame that is currently painting by
+ // referencing us to paint again. See bug 839958 comment 7. Hopefully we
+ // will break that loop more convincingly at some point.
+ RemoveStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+
+ nsPresContext* presContext = PresContext();
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (!kid) {
+ return;
+ }
+
+ RefPtr<gfxContext> renderingContext =
+ presContext->PresShell()->CreateReferenceRenderingContext();
+
+ if (UpdateFontSizeScaleFactor()) {
+ // If the font size scale factor changed, we need the block to report
+ // an updated preferred width.
+ kid->MarkIntrinsicISizesDirty();
+ }
+
+ nscoord inlineSize = kid->GetPrefISize(renderingContext);
+ WritingMode wm = kid->GetWritingMode();
+ ReflowInput reflowInput(presContext, kid, renderingContext,
+ LogicalSize(wm, inlineSize, NS_UNCONSTRAINEDSIZE));
+ ReflowOutput desiredSize(reflowInput);
+ nsReflowStatus status;
+
+ NS_ASSERTION(
+ reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) &&
+ reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
+ "style system should ensure that :-moz-svg-text "
+ "does not get styled");
+
+ kid->Reflow(presContext, desiredSize, reflowInput, status);
+ kid->DidReflow(presContext, &reflowInput);
+ kid->SetSize(wm, desiredSize.Size(wm));
+}
+
+// Usable font size range in devpixels / user-units
+#define CLAMP_MIN_SIZE 8.0
+#define CLAMP_MAX_SIZE 200.0
+#define PRECISE_SIZE 200.0
+
+bool SVGTextFrame::UpdateFontSizeScaleFactor() {
+ double oldFontSizeScaleFactor = mFontSizeScaleFactor;
+
+ nsPresContext* presContext = PresContext();
+
+ bool geometricPrecision = false;
+ CSSCoord min = std::numeric_limits<float>::max();
+ CSSCoord max = std::numeric_limits<float>::min();
+ bool anyText = false;
+
+ // Find the minimum and maximum font sizes used over all the
+ // nsTextFrames.
+ TextFrameIterator it(this);
+ nsTextFrame* f = it.Current();
+ while (f) {
+ if (!geometricPrecision) {
+ // Unfortunately we can't treat text-rendering:geometricPrecision
+ // separately for each text frame.
+ geometricPrecision = f->StyleText()->mTextRendering ==
+ StyleTextRendering::Geometricprecision;
+ }
+ const auto& fontSize = f->StyleFont()->mFont.size;
+ if (!fontSize.IsZero()) {
+ min = std::min(min, fontSize.ToCSSPixels());
+ max = std::max(max, fontSize.ToCSSPixels());
+ anyText = true;
+ }
+ f = it.Next();
+ }
+
+ if (!anyText) {
+ // No text, so no need for scaling.
+ mFontSizeScaleFactor = 1.0;
+ return mFontSizeScaleFactor != oldFontSizeScaleFactor;
+ }
+
+ if (geometricPrecision) {
+ // We want to ensure minSize is scaled to PRECISE_SIZE.
+ mFontSizeScaleFactor = PRECISE_SIZE / min;
+ return mFontSizeScaleFactor != oldFontSizeScaleFactor;
+ }
+
+ // When we are non-display, we could be painted in different coordinate
+ // spaces, and we don't want to have to reflow for each of these. We
+ // just assume that the context scale is 1.0 for them all, so we don't
+ // get stuck with a font size scale factor based on whichever referencing
+ // frame happens to reflow first.
+ double contextScale = 1.0;
+ if (!(mState & NS_FRAME_IS_NONDISPLAY)) {
+ gfxMatrix m(GetCanvasTM());
+ if (!m.IsSingular()) {
+ contextScale = GetContextScale(m);
+ if (!std::isfinite(contextScale)) {
+ contextScale = 1.0f;
+ }
+ }
+ }
+ mLastContextScale = contextScale;
+
+ // But we want to ignore any scaling required due to HiDPI displays, since
+ // regular CSS text frames will still create text runs using the font size
+ // in CSS pixels, and we want SVG text to have the same rendering as HTML
+ // text for regular font sizes.
+ float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(
+ presContext->AppUnitsPerDevPixel());
+ contextScale *= cssPxPerDevPx;
+
+ double minTextRunSize = min * contextScale;
+ double maxTextRunSize = max * contextScale;
+
+ if (minTextRunSize >= CLAMP_MIN_SIZE && maxTextRunSize <= CLAMP_MAX_SIZE) {
+ // We are already in the ideal font size range for all text frames,
+ // so we only have to take into account the contextScale.
+ mFontSizeScaleFactor = contextScale;
+ } else if (max / min > CLAMP_MAX_SIZE / CLAMP_MIN_SIZE) {
+ // We can't scale the font sizes so that all of the text frames lie
+ // within our ideal font size range.
+ // Heuristically, if the maxTextRunSize is within the CLAMP_MAX_SIZE
+ // as a reasonable value, it's likely to be the user's intent to
+ // get a valid font for the maxTextRunSize one, we should honor it.
+ // The same for minTextRunSize.
+ if (maxTextRunSize <= CLAMP_MAX_SIZE) {
+ mFontSizeScaleFactor = CLAMP_MAX_SIZE / max;
+ } else if (minTextRunSize >= CLAMP_MIN_SIZE) {
+ mFontSizeScaleFactor = CLAMP_MIN_SIZE / min;
+ } else {
+ // So maxTextRunSize is too big, minTextRunSize is too small,
+ // we can't really do anything for this case, just leave it as is.
+ mFontSizeScaleFactor = contextScale;
+ }
+ } else if (minTextRunSize < CLAMP_MIN_SIZE) {
+ mFontSizeScaleFactor = CLAMP_MIN_SIZE / min;
+ } else {
+ mFontSizeScaleFactor = CLAMP_MAX_SIZE / max;
+ }
+
+ return mFontSizeScaleFactor != oldFontSizeScaleFactor;
+}
+
+double SVGTextFrame::GetFontSizeScaleFactor() const {
+ return mFontSizeScaleFactor;
+}
+
+/**
+ * Take aPoint, which is in the <text> element's user space, and convert
+ * it to the appropriate frame user space of aChildFrame according to
+ * which rendered run the point hits.
+ */
+Point SVGTextFrame::TransformFramePointToTextChild(
+ const Point& aPoint, const nsIFrame* aChildFrame) {
+ NS_ASSERTION(aChildFrame && nsLayoutUtils::GetClosestFrameOfType(
+ aChildFrame->GetParent(),
+ LayoutFrameType::SVGText) == this,
+ "aChildFrame must be a descendant of this frame");
+
+ UpdateGlyphPositioning();
+
+ nsPresContext* presContext = PresContext();
+
+ // Add in the mRect offset to aPoint, as that will have been taken into
+ // account when transforming the point from the ancestor frame down
+ // to this one.
+ float cssPxPerDevPx = nsPresContext::AppUnitsToFloatCSSPixels(
+ presContext->AppUnitsPerDevPixel());
+ float factor = AppUnitsPerCSSPixel();
+ Point framePosition(NSAppUnitsToFloatPixels(mRect.x, factor),
+ NSAppUnitsToFloatPixels(mRect.y, factor));
+ Point pointInUserSpace = aPoint * cssPxPerDevPx + framePosition;
+
+ // Find the closest rendered run for the text frames beneath aChildFrame.
+ TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames,
+ aChildFrame);
+ TextRenderedRun hit;
+ gfxPoint pointInRun;
+ nscoord dx = nscoord_MAX;
+ nscoord dy = nscoord_MAX;
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ uint32_t flags = TextRenderedRun::eIncludeFill |
+ TextRenderedRun::eIncludeStroke |
+ TextRenderedRun::eNoHorizontalOverflow;
+ gfxRect runRect =
+ run.GetRunUserSpaceRect(presContext, flags).ToThebesRect();
+
+ gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext);
+ if (!m.Invert()) {
+ return aPoint;
+ }
+ gfxPoint pointInRunUserSpace =
+ m.TransformPoint(ThebesPoint(pointInUserSpace));
+
+ if (Inside(runRect, pointInRunUserSpace)) {
+ // The point was inside the rendered run's rect, so we choose it.
+ dx = 0;
+ dy = 0;
+ pointInRun = pointInRunUserSpace;
+ hit = run;
+ } else if (nsLayoutUtils::PointIsCloserToRect(pointInRunUserSpace, runRect,
+ dx, dy)) {
+ // The point was closer to this rendered run's rect than any others
+ // we've seen so far.
+ pointInRun.x =
+ clamped(pointInRunUserSpace.x.value, runRect.X(), runRect.XMost());
+ pointInRun.y =
+ clamped(pointInRunUserSpace.y.value, runRect.Y(), runRect.YMost());
+ hit = run;
+ }
+ }
+
+ if (!hit.mFrame) {
+ // We didn't find any rendered runs for the frame.
+ return aPoint;
+ }
+
+ // Return the point in user units relative to the nsTextFrame,
+ // but taking into account mFontSizeScaleFactor.
+ gfxMatrix m = hit.GetTransformFromRunUserSpaceToFrameUserSpace(presContext);
+ m.PreScale(mFontSizeScaleFactor, mFontSizeScaleFactor);
+ return ToPoint(m.TransformPoint(pointInRun) / cssPxPerDevPx);
+}
+
+/**
+ * For each rendered run beneath aChildFrame, translate aRect from
+ * aChildFrame to the run's text frame, transform it then into
+ * the run's frame user space, intersect it with the run's
+ * frame user space rect, then transform it up to user space.
+ * The result is the union of all of these.
+ */
+gfxRect SVGTextFrame::TransformFrameRectFromTextChild(
+ const nsRect& aRect, const nsIFrame* aChildFrame) {
+ NS_ASSERTION(aChildFrame && nsLayoutUtils::GetClosestFrameOfType(
+ aChildFrame->GetParent(),
+ LayoutFrameType::SVGText) == this,
+ "aChildFrame must be a descendant of this frame");
+
+ UpdateGlyphPositioning();
+
+ nsPresContext* presContext = PresContext();
+
+ gfxRect result;
+ TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames,
+ aChildFrame);
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ // First, translate aRect from aChildFrame to this run's frame.
+ nsRect rectInTextFrame = aRect + aChildFrame->GetOffsetTo(run.mFrame);
+
+ // Scale it into frame user space.
+ gfxRect rectInFrameUserSpace = AppUnitsToFloatCSSPixels(
+ gfxRect(rectInTextFrame.x, rectInTextFrame.y, rectInTextFrame.width,
+ rectInTextFrame.height),
+ presContext);
+
+ // Intersect it with the run.
+ uint32_t flags =
+ TextRenderedRun::eIncludeFill | TextRenderedRun::eIncludeStroke;
+
+ if (rectInFrameUserSpace.IntersectRect(
+ rectInFrameUserSpace,
+ run.GetFrameUserSpaceRect(presContext, flags).ToThebesRect())) {
+ // Transform it up to user space of the <text>
+ gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext);
+ gfxRect rectInUserSpace = m.TransformRect(rectInFrameUserSpace);
+
+ // Union it into the result.
+ result.UnionRect(result, rectInUserSpace);
+ }
+ }
+
+ // Subtract the mRect offset from the result, as our user space for
+ // this frame is relative to the top-left of mRect.
+ float factor = AppUnitsPerCSSPixel();
+ gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor),
+ NSAppUnitsToFloatPixels(mRect.y, factor));
+
+ return result - framePosition;
+}
+
+Rect SVGTextFrame::TransformFrameRectFromTextChild(
+ const Rect& aRect, const nsIFrame* aChildFrame) {
+ nscoord appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
+ nsRect r = LayoutDevicePixel::ToAppUnits(
+ LayoutDeviceRect::FromUnknownRect(aRect), appUnitsPerDevPixel);
+ gfxRect resultCssUnits = TransformFrameRectFromTextChild(r, aChildFrame);
+ float devPixelPerCSSPixel =
+ float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel;
+ resultCssUnits.Scale(devPixelPerCSSPixel);
+ return ToRect(resultCssUnits);
+}
+
+Point SVGTextFrame::TransformFramePointFromTextChild(
+ const Point& aPoint, const nsIFrame* aChildFrame) {
+ return TransformFrameRectFromTextChild(Rect(aPoint, Size(1, 1)), aChildFrame)
+ .TopLeft();
+}
+
+void SVGTextFrame::AppendDirectlyOwnedAnonBoxes(
+ nsTArray<OwnedAnonBox>& aResult) {
+ MOZ_ASSERT(PrincipalChildList().FirstChild(), "Must have our anon box");
+ aResult.AppendElement(OwnedAnonBox(PrincipalChildList().FirstChild()));
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGTextFrame.h b/layout/svg/SVGTextFrame.h
new file mode 100644
index 0000000000..756088cb96
--- /dev/null
+++ b/layout/svg/SVGTextFrame.h
@@ -0,0 +1,589 @@
+/* -*- 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 LAYOUT_SVG_SVGTEXTFRAME_H_
+#define LAYOUT_SVG_SVGTEXTFRAME_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/PresShellForwards.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SVGContainerFrame.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxMatrix.h"
+#include "gfxRect.h"
+#include "gfxTextRun.h"
+#include "nsIContent.h" // for GetContent
+#include "nsStubMutationObserver.h"
+#include "nsTextFrame.h"
+
+class gfxContext;
+
+namespace mozilla {
+
+class CharIterator;
+class DisplaySVGText;
+class SVGTextFrame;
+class TextFrameIterator;
+class TextNodeCorrespondenceRecorder;
+struct TextRenderedRun;
+class TextRenderedRunIterator;
+
+namespace dom {
+struct DOMPointInit;
+class DOMSVGPoint;
+class SVGRect;
+class SVGGeometryElement;
+} // namespace dom
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGTextFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+/**
+ * Information about the positioning for a single character in an SVG <text>
+ * element.
+ *
+ * During SVG text layout, we use infinity values to represent positions and
+ * rotations that are not explicitly specified with x/y/rotate attributes.
+ */
+struct CharPosition {
+ CharPosition()
+ : mAngle(0),
+ mHidden(false),
+ mUnaddressable(false),
+ mClusterOrLigatureGroupMiddle(false),
+ mRunBoundary(false),
+ mStartOfChunk(false) {}
+
+ CharPosition(gfxPoint aPosition, double aAngle)
+ : mPosition(aPosition),
+ mAngle(aAngle),
+ mHidden(false),
+ mUnaddressable(false),
+ mClusterOrLigatureGroupMiddle(false),
+ mRunBoundary(false),
+ mStartOfChunk(false) {}
+
+ static CharPosition Unspecified(bool aUnaddressable) {
+ CharPosition cp(UnspecifiedPoint(), UnspecifiedAngle());
+ cp.mUnaddressable = aUnaddressable;
+ return cp;
+ }
+
+ bool IsAngleSpecified() const { return mAngle != UnspecifiedAngle(); }
+
+ bool IsXSpecified() const { return mPosition.x != UnspecifiedCoord(); }
+
+ bool IsYSpecified() const { return mPosition.y != UnspecifiedCoord(); }
+
+ gfxPoint mPosition;
+ double mAngle;
+
+ // not displayed due to falling off the end of a <textPath>
+ bool mHidden;
+
+ // skipped in positioning attributes due to being collapsed-away white space
+ bool mUnaddressable;
+
+ // a preceding character is what positioning attributes address
+ bool mClusterOrLigatureGroupMiddle;
+
+ // rendering is split here since an explicit position or rotation was given
+ bool mRunBoundary;
+
+ // an anchored chunk begins here
+ bool mStartOfChunk;
+
+ private:
+ static gfxFloat UnspecifiedCoord() {
+ return std::numeric_limits<gfxFloat>::infinity();
+ }
+
+ static double UnspecifiedAngle() {
+ return std::numeric_limits<double>::infinity();
+ }
+
+ static gfxPoint UnspecifiedPoint() {
+ return gfxPoint(UnspecifiedCoord(), UnspecifiedCoord());
+ }
+};
+
+/**
+ * A runnable to mark glyph positions as needing to be recomputed
+ * and to invalid the bounds of the SVGTextFrame frame.
+ */
+class GlyphMetricsUpdater : public Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ explicit GlyphMetricsUpdater(SVGTextFrame* aFrame)
+ : Runnable("GlyphMetricsUpdater"), mFrame(aFrame) {}
+ static void Run(SVGTextFrame* aFrame);
+ void Revoke() { mFrame = nullptr; }
+
+ private:
+ SVGTextFrame* mFrame;
+};
+
+/**
+ * Frame class for SVG <text> elements.
+ *
+ * An SVGTextFrame manages SVG text layout, painting and interaction for
+ * all descendent text content elements. The frame tree will look like this:
+ *
+ * SVGTextFrame -- for <text>
+ * <anonymous block frame>
+ * ns{Block,Inline,Text}Frames -- for text nodes, <tspan>s, <a>s, etc.
+ *
+ * SVG text layout is done by:
+ *
+ * 1. Reflowing the anonymous block frame.
+ * 2. Inspecting the (app unit) positions of the glyph for each character in
+ * the nsTextFrames underneath the anonymous block frame.
+ * 3. Determining the (user unit) positions for each character in the <text>
+ * using the x/y/dx/dy/rotate attributes on all the text content elements,
+ * and using the step 2 results to fill in any gaps.
+ * 4. Applying any other SVG specific text layout (anchoring and text paths)
+ * to the positions computed in step 3.
+ *
+ * Rendering of the text is done by splitting up each nsTextFrame into ranges
+ * that can be contiguously painted. (For example <text x="10 20">abcd</text>
+ * would have two contiguous ranges: one for the "a" and one for the "bcd".)
+ * Each range is called a "text rendered run", represented by a TextRenderedRun
+ * object. The TextRenderedRunIterator class performs that splitting and
+ * returns a TextRenderedRun for each bit of text to be painted separately.
+ *
+ * Each rendered run is painted by calling nsTextFrame::PaintText. If the text
+ * formatting is simple enough (solid fill, no stroking, etc.), PaintText will
+ * itself do the painting. Otherwise, a DrawPathCallback is passed to
+ * PaintText so that we can fill the text geometry with SVG paint servers.
+ */
+class SVGTextFrame final : public SVGDisplayContainerFrame {
+ friend nsIFrame* ::NS_NewSVGTextFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ friend class CharIterator;
+ friend class DisplaySVGText;
+ friend class GlyphMetricsUpdater;
+ friend class MutationObserver;
+ friend class TextFrameIterator;
+ friend class TextNodeCorrespondenceRecorder;
+ friend struct TextRenderedRun;
+ friend class TextRenderedRunIterator;
+
+ using Range = gfxTextRun::Range;
+ using DrawTarget = gfx::DrawTarget;
+ using Path = gfx::Path;
+ using Point = gfx::Point;
+ using Rect = gfx::Rect;
+
+ protected:
+ explicit SVGTextFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : SVGDisplayContainerFrame(aStyle, aPresContext, kClassID),
+ mTrailingUndisplayedCharacters(0),
+ mFontSizeScaleFactor(1.0f),
+ mLastContextScale(1.0f),
+ mLengthAdjustScaleFactor(1.0f) {
+ AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY |
+ NS_STATE_SVG_POSITIONING_DIRTY);
+ }
+
+ ~SVGTextFrame() = default;
+
+ public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS(SVGTextFrame)
+
+ // nsIFrame:
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ nsresult AttributeChanged(int32_t aNamespaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ nsContainerFrame* GetContentInsertionFrame() override {
+ return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
+ }
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGText"_ns, aResult);
+ }
+#endif
+
+ /**
+ * Finds the nsTextFrame for the closest rendered run to the specified point.
+ */
+ void FindCloserFrameForSelection(
+ const nsPoint& aPoint, FrameWithDistance* aCurrentBestFrame) override;
+
+ // ISVGDisplayableFrame interface:
+ void NotifySVGChanged(uint32_t aFlags) override;
+ void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect = nullptr) override;
+ nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
+ void ReflowSVG() override;
+ SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace,
+ uint32_t aFlags) override;
+
+ // SVG DOM text methods:
+ uint32_t GetNumberOfChars(nsIContent* aContent);
+ float GetComputedTextLength(nsIContent* aContent);
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void SelectSubString(nsIContent* aContent,
+ uint32_t charnum,
+ uint32_t nchars,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT
+ float GetSubStringLength(nsIContent* aContent, uint32_t charnum,
+ uint32_t nchars, ErrorResult& aRv);
+ int32_t GetCharNumAtPosition(nsIContent* aContent,
+ const dom::DOMPointInit& aPoint);
+
+ already_AddRefed<dom::DOMSVGPoint> GetStartPositionOfChar(
+ nsIContent* aContent, uint32_t aCharNum, ErrorResult& aRv);
+ already_AddRefed<dom::DOMSVGPoint> GetEndPositionOfChar(nsIContent* aContent,
+ uint32_t aCharNum,
+ ErrorResult& aRv);
+ already_AddRefed<dom::SVGRect> GetExtentOfChar(nsIContent* aContent,
+ uint32_t aCharNum,
+ ErrorResult& aRv);
+ float GetRotationOfChar(nsIContent* aContent, uint32_t aCharNum,
+ ErrorResult& aRv);
+
+ // SVGTextFrame methods:
+
+ /**
+ * Handles a base or animated attribute value change to a descendant
+ * text content element.
+ */
+ void HandleAttributeChangeInDescendant(dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute);
+
+ /**
+ * Calls ScheduleReflowSVGNonDisplayText if this is a non-display frame,
+ * and SVGUtils::ScheduleReflowSVG otherwise.
+ */
+ void ScheduleReflowSVG();
+
+ /**
+ * Reflows the anonymous block frame of this non-display SVGTextFrame.
+ *
+ * When we are under SVGDisplayContainerFrame::ReflowSVG, we need to
+ * reflow any SVGTextFrame frames in the subtree in case they are
+ * being observed (by being for example in a <mask>) and the change
+ * that caused the reflow would not already have caused a reflow.
+ *
+ * Note that displayed SVGTextFrames are reflowed as needed, when PaintSVG
+ * is called or some SVG DOM method is called on the element.
+ */
+ void ReflowSVGNonDisplayText();
+
+ /**
+ * This is a function that behaves similarly to SVGUtils::ScheduleReflowSVG,
+ * but which will skip over any ancestor non-display container frames on the
+ * way to the SVGOuterSVGFrame. It exists for the situation where a
+ * non-display <text> element has changed and needs to ensure ReflowSVG will
+ * be called on its closest display container frame, so that
+ * SVGDisplayContainerFrame::ReflowSVG will call ReflowSVGNonDisplayText on
+ * it.
+ *
+ * We have to do this in two cases: in response to a style change on a
+ * non-display <text>, where aReason will be
+ * IntrinsicDirty::FrameAncestorsAndDescendants (the common case), and also in
+ * response to glyphs changes on non-display <text> (i.e., animated
+ * SVG-in-OpenType glyphs), in which case aReason will be None, since layout
+ * doesn't need to be recomputed.
+ */
+ void ScheduleReflowSVGNonDisplayText(IntrinsicDirty aReason);
+
+ /**
+ * Updates the mFontSizeScaleFactor value by looking at the range of
+ * font-sizes used within the <text>.
+ *
+ * @return Whether mFontSizeScaleFactor changed.
+ */
+ bool UpdateFontSizeScaleFactor();
+
+ double GetFontSizeScaleFactor() const;
+
+ /**
+ * Takes a point from the <text> element's user space and
+ * converts it to the appropriate frame user space of aChildFrame,
+ * according to which rendered run the point hits.
+ */
+ Point TransformFramePointToTextChild(const Point& aPoint,
+ const nsIFrame* aChildFrame);
+
+ /**
+ * Takes an app unit rectangle in the coordinate space of a given descendant
+ * frame of this frame, and returns a rectangle in the <text> element's user
+ * space that covers all parts of rendered runs that intersect with the
+ * rectangle.
+ */
+ gfxRect TransformFrameRectFromTextChild(const nsRect& aRect,
+ const nsIFrame* aChildFrame);
+
+ /** As above, but taking and returning a device px rect. */
+ Rect TransformFrameRectFromTextChild(const Rect& aRect,
+ const nsIFrame* aChildFrame);
+
+ /** As above, but with a single point */
+ Point TransformFramePointFromTextChild(const Point& aPoint,
+ const nsIFrame* aChildFrame);
+
+ // Return our ::-moz-svg-text anonymous box.
+ void AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) override;
+
+ private:
+ /**
+ * Mutation observer used to watch for text positioning attribute changes
+ * on descendent text content elements (like <tspan>s).
+ */
+ class MutationObserver final : public nsStubMutationObserver {
+ public:
+ explicit MutationObserver(SVGTextFrame* aFrame) : mFrame(aFrame) {
+ MOZ_ASSERT(mFrame, "MutationObserver needs a non-null frame");
+ mFrame->GetContent()->AddMutationObserver(this);
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+
+ private:
+ ~MutationObserver() { mFrame->GetContent()->RemoveMutationObserver(this); }
+
+ SVGTextFrame* const mFrame;
+ };
+
+ /**
+ * Resolves Bidi for the anonymous block child if it needs it.
+ */
+ void MaybeResolveBidiForAnonymousBlockChild();
+
+ /**
+ * Reflows the anonymous block child if it is dirty or has dirty
+ * children, or if the SVGTextFrame itself is dirty.
+ */
+ void MaybeReflowAnonymousBlockChild();
+
+ /**
+ * Performs the actual work of reflowing the anonymous block child.
+ */
+ void DoReflow();
+
+ /**
+ * Schedules mPositions to be recomputed and the covered region to be
+ * updated.
+ */
+ void NotifyGlyphMetricsChange();
+
+ /**
+ * Recomputes mPositions by calling DoGlyphPositioning if this information
+ * is out of date.
+ */
+ void UpdateGlyphPositioning();
+
+ /**
+ * Populates mPositions with positioning information for each character
+ * within the <text>.
+ */
+ void DoGlyphPositioning();
+
+ /**
+ * This fallback version of GetSubStringLength that flushes layout and takes
+ * into account glyph positioning. As per the SVG 2 spec, typically glyph
+ * positioning does not affect the results of getSubStringLength, but one
+ * exception is text in a textPath where we need to ignore characters that
+ * fall off the end of the textPath path.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ float GetSubStringLengthSlowFallback(nsIContent* aContent, uint32_t charnum,
+ uint32_t nchars, ErrorResult& aRv);
+
+ /**
+ * Converts the specified index into mPositions to an addressable
+ * character index (as can be used with the SVG DOM text methods)
+ * relative to the specified text child content element.
+ *
+ * @param aIndex The global character index.
+ * @param aContent The descendant text child content element that
+ * the returned addressable index will be relative to; null
+ * means the same as the <text> element.
+ * @return The addressable index, or -1 if the index cannot be
+ * represented as an addressable index relative to aContent.
+ */
+ int32_t ConvertTextElementCharIndexToAddressableIndex(int32_t aIndex,
+ nsIContent* aContent);
+
+ /**
+ * Recursive helper for ResolvePositions below.
+ *
+ * @param aContent The current node.
+ * @param aIndex (in/out) The current character index.
+ * @param aInTextPath Whether we are currently under a <textPath> element.
+ * @param aForceStartOfChunk (in/out) Whether the next character we find
+ * should start a new anchored chunk.
+ * @param aDeltas (in/out) Receives the resolved dx/dy values for each
+ * character.
+ * @return false if we discover that mPositions did not have enough
+ * elements; true otherwise.
+ */
+ bool ResolvePositionsForNode(nsIContent* aContent, uint32_t& aIndex,
+ bool aInTextPath, bool& aForceStartOfChunk,
+ nsTArray<gfxPoint>& aDeltas);
+
+ /**
+ * Initializes mPositions with character position information based on
+ * x/y/rotate attributes, leaving unspecified values in the array if a
+ * position was not given for that character. Also fills aDeltas with values
+ * based on dx/dy attributes.
+ *
+ * @param aDeltas (in/out) Receives the resolved dx/dy values for each
+ * character.
+ * @param aRunPerGlyph Whether mPositions should record that a new run begins
+ * at each glyph.
+ * @return false if we did not record any positions (due to having no
+ * displayed characters) or if we discover that mPositions did not have
+ * enough elements; true otherwise.
+ */
+ bool ResolvePositions(nsTArray<gfxPoint>& aDeltas, bool aRunPerGlyph);
+
+ /**
+ * Determines the position, in app units, of each character in the <text> as
+ * laid out by reflow, and appends them to aPositions. Any characters that
+ * are undisplayed or trimmed away just get the last position.
+ */
+ void DetermineCharPositions(nsTArray<nsPoint>& aPositions);
+
+ /**
+ * Sets mStartOfChunk to true for each character in mPositions that starts a
+ * line of text.
+ */
+ void AdjustChunksForLineBreaks();
+
+ /**
+ * Adjusts recorded character positions in mPositions to account for glyph
+ * boundaries. Four things are done:
+ *
+ * 1. mClusterOrLigatureGroupMiddle is set to true for all such characters.
+ *
+ * 2. Any run and anchored chunk boundaries that begin in the middle of a
+ * cluster/ligature group get moved to the start of the next
+ * cluster/ligature group.
+ *
+ * 3. The position of any character in the middle of a cluster/ligature
+ * group is updated to take into account partial ligatures and any
+ * rotation the glyph as a whole has. (The values that come out of
+ * DetermineCharPositions which then get written into mPositions in
+ * ResolvePositions store the same position value for each part of the
+ * ligature.)
+ *
+ * 4. The rotation of any character in the middle of a cluster/ligature
+ * group is set to the rotation of the first character.
+ */
+ void AdjustPositionsForClusters();
+
+ /**
+ * Updates the character positions stored in mPositions to account for
+ * text anchoring.
+ */
+ void DoAnchoring();
+
+ /**
+ * Updates character positions in mPositions for those characters inside a
+ * <textPath>.
+ */
+ void DoTextPathLayout();
+
+ /**
+ * Returns whether we need to render the text using
+ * nsTextFrame::DrawPathCallbacks rather than directly painting
+ * the text frames.
+ *
+ * @param aShouldPaintSVGGlyphs (out) Whether SVG glyphs in the text
+ * should be painted.
+ */
+ bool ShouldRenderAsPath(nsTextFrame* aFrame, bool& aShouldPaintSVGGlyphs);
+
+ // Methods to get information for a <textPath> frame.
+ already_AddRefed<Path> GetTextPath(nsIFrame* aTextPathFrame);
+ gfxFloat GetOffsetScale(nsIFrame* aTextPathFrame);
+ gfxFloat GetStartOffset(nsIFrame* aTextPathFrame);
+
+ /**
+ * The MutationObserver we have registered for the <text> element subtree.
+ */
+ RefPtr<MutationObserver> mMutationObserver;
+
+ /**
+ * The number of characters in the DOM after the final nsTextFrame. For
+ * example, with
+ *
+ * <text>abcd<tspan display="none">ef</tspan></text>
+ *
+ * mTrailingUndisplayedCharacters would be 2.
+ */
+ uint32_t mTrailingUndisplayedCharacters;
+
+ /**
+ * Computed position information for each DOM character within the <text>.
+ */
+ nsTArray<CharPosition> mPositions;
+
+ /**
+ * mFontSizeScaleFactor is used to cause the nsTextFrames to create text
+ * runs with a font size different from the actual font-size property value.
+ * This is used so that, for example with:
+ *
+ * <svg>
+ * <g transform="scale(2)">
+ * <text font-size="10">abc</text>
+ * </g>
+ * </svg>
+ *
+ * a font size of 20 would be used. It's preferable to use a font size that
+ * is identical or close to the size that the text will appear on the screen,
+ * because at very small or large font sizes, text metrics will be computed
+ * differently due to the limited precision that text runs have.
+ *
+ * mFontSizeScaleFactor is the amount the actual font-size property value
+ * should be multiplied by to cause the text run font size to (a) be within a
+ * "reasonable" range, and (b) be close to the actual size to be painted on
+ * screen. (The "reasonable" range as determined by some #defines in
+ * SVGTextFrame.cpp is 8..200.)
+ */
+ float mFontSizeScaleFactor;
+
+ /**
+ * The scale of the context that we last used to compute mFontSizeScaleFactor.
+ * We record this so that we can tell when our scale transform has changed
+ * enough to warrant reflowing the text.
+ */
+ float mLastContextScale;
+
+ /**
+ * The amount that we need to scale each rendered run to account for
+ * lengthAdjust="spacingAndGlyphs".
+ */
+ float mLengthAdjustScaleFactor;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGTEXTFRAME_H_
diff --git a/layout/svg/SVGUseFrame.cpp b/layout/svg/SVGUseFrame.cpp
new file mode 100644
index 0000000000..6442657f44
--- /dev/null
+++ b/layout/svg/SVGUseFrame.cpp
@@ -0,0 +1,161 @@
+/* -*- 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 "SVGUseFrame.h"
+
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/MutationEvent.h"
+#include "mozilla/dom/SVGUseElement.h"
+#include "nsLayoutUtils.h"
+
+using namespace mozilla::dom;
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsIFrame* NS_NewSVGUseFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGUseFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGUseFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods:
+
+void SVGUseFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::use),
+ "Content is not an SVG use!");
+
+ mHasValidDimensions =
+ static_cast<SVGUseElement*>(aContent)->HasValidDimensions();
+
+ SVGGFrame::Init(aContent, aParent, aPrevInFlow);
+}
+
+nsresult SVGUseFrame::AttributeChanged(int32_t aNamespaceID, nsAtom* aAttribute,
+ int32_t aModType) {
+ // Currently our SMIL implementation does not modify the DOM attributes. Once
+ // we implement the SVG 2 SMIL behaviour this can be removed
+ // SVGUseElement::AfterSetAttr's implementation will be sufficient.
+ if (aModType == MutationEvent_Binding::SMIL) {
+ auto* content = SVGUseElement::FromNode(GetContent());
+ content->ProcessAttributeChange(aNamespaceID, aAttribute);
+ }
+
+ return SVGGFrame::AttributeChanged(aNamespaceID, aAttribute, aModType);
+}
+
+void SVGUseFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
+ SVGGFrame::DidSetComputedStyle(aOldComputedStyle);
+
+ if (!aOldComputedStyle) {
+ return;
+ }
+ const auto* newSVGReset = StyleSVGReset();
+ const auto* oldSVGReset = aOldComputedStyle->StyleSVGReset();
+
+ if (newSVGReset->mX != oldSVGReset->mX ||
+ newSVGReset->mY != oldSVGReset->mY) {
+ // make sure our cached transform matrix gets (lazily) updated
+ mCanvasTM = nullptr;
+ SVGUtils::ScheduleReflowSVG(this);
+ SVGUtils::NotifyChildrenOfSVGChange(this, TRANSFORM_CHANGED);
+ }
+}
+
+void SVGUseFrame::DimensionAttributeChanged(bool aHadValidDimensions,
+ bool aAttributeIsUsed) {
+ bool invalidate = aAttributeIsUsed;
+ if (mHasValidDimensions != aHadValidDimensions) {
+ mHasValidDimensions = !mHasValidDimensions;
+ invalidate = true;
+ }
+
+ if (invalidate) {
+ nsLayoutUtils::PostRestyleEvent(GetContent()->AsElement(), RestyleHint{0},
+ nsChangeHint_InvalidateRenderingObservers);
+ SVGUtils::ScheduleReflowSVG(this);
+ }
+}
+
+void SVGUseFrame::HrefChanged() {
+ nsLayoutUtils::PostRestyleEvent(GetContent()->AsElement(), RestyleHint{0},
+ nsChangeHint_InvalidateRenderingObservers);
+ SVGUtils::ScheduleReflowSVG(this);
+}
+
+//----------------------------------------------------------------------
+// ISVGDisplayableFrame methods
+
+void SVGUseFrame::ReflowSVG() {
+ // We only handle x/y offset here, since any width/height that is in force is
+ // handled by the SVGOuterSVGFrame for the anonymous <svg> that will be
+ // created for that purpose.
+ auto [x, y] = ResolvePosition();
+ mRect.MoveTo(nsLayoutUtils::RoundGfxRectToAppRect(gfxRect(x, y, 0, 0),
+ AppUnitsPerCSSPixel())
+ .TopLeft());
+
+ // If we have a filter, we need to invalidate ourselves because filter
+ // output can change even if none of our descendants need repainting.
+ if (StyleEffects()->HasFilters()) {
+ InvalidateFrame();
+ }
+
+ SVGGFrame::ReflowSVG();
+}
+
+void SVGUseFrame::NotifySVGChanged(uint32_t aFlags) {
+ if (aFlags & COORD_CONTEXT_CHANGED && !(aFlags & TRANSFORM_CHANGED)) {
+ // Coordinate context changes affect mCanvasTM if we have a
+ // percentage 'x' or 'y'
+ if (StyleSVGReset()->mX.HasPercent() || StyleSVGReset()->mY.HasPercent()) {
+ aFlags |= TRANSFORM_CHANGED;
+ // Ancestor changes can't affect how we render from the perspective of
+ // any rendering observers that we may have, so we don't need to
+ // invalidate them. We also don't need to invalidate ourself, since our
+ // changed ancestor will have invalidated its entire area, which includes
+ // our area.
+ // For perf reasons we call this before calling NotifySVGChanged() below.
+ SVGUtils::ScheduleReflowSVG(this);
+ }
+ }
+
+ // We don't remove the TRANSFORM_CHANGED flag here if we have a viewBox or
+ // non-percentage width/height, since if they're set then they are cloned to
+ // an anonymous child <svg>, and its SVGInnerSVGFrame will do that.
+
+ SVGGFrame::NotifySVGChanged(aFlags);
+}
+
+SVGBBox SVGUseFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace,
+ uint32_t aFlags) {
+ SVGBBox bbox =
+ SVGDisplayContainerFrame::GetBBoxContribution(aToBBoxUserspace, aFlags);
+
+ if (aFlags & SVGUtils::eForGetClientRects) {
+ auto [x, y] = ResolvePosition();
+ bbox.MoveBy(x, y);
+ }
+ return bbox;
+}
+
+std::pair<float, float> SVGUseFrame::ResolvePosition() const {
+ auto* content = SVGUseElement::FromNode(GetContent());
+ return std::make_pair(SVGContentUtils::CoordToFloat(
+ content, StyleSVGReset()->mX, SVGContentUtils::X),
+ SVGContentUtils::CoordToFloat(
+ content, StyleSVGReset()->mY, SVGContentUtils::Y));
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGUseFrame.h b/layout/svg/SVGUseFrame.h
new file mode 100644
index 0000000000..fa2bf231b2
--- /dev/null
+++ b/layout/svg/SVGUseFrame.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 LAYOUT_SVG_SVGUSEFRAME_H_
+#define LAYOUT_SVG_SVGUSEFRAME_H_
+
+// Keep in (case-insensitive) order:
+#include "SVGGFrame.h"
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGUseFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+class SVGUseFrame final : public SVGGFrame {
+ friend nsIFrame* ::NS_NewSVGUseFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ protected:
+ explicit SVGUseFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : SVGGFrame(aStyle, aPresContext, kClassID), mHasValidDimensions(true) {}
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGUseFrame)
+
+ // nsIFrame interface:
+ void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ // Called when the href attributes changed.
+ void HrefChanged();
+
+ // Called when the width or height attributes changed.
+ void DimensionAttributeChanged(bool aHadValidDimensions,
+ bool aAttributeIsUsed);
+
+ nsresult AttributeChanged(int32_t aNamespaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+ void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGUse"_ns, aResult);
+ }
+#endif
+
+ // ISVGDisplayableFrame interface:
+ void ReflowSVG() override;
+ void NotifySVGChanged(uint32_t aFlags) override;
+ SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace,
+ uint32_t aFlags) override;
+
+ private:
+ std::pair<float, float> ResolvePosition() const;
+
+ bool mHasValidDimensions;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGUSEFRAME_H_
diff --git a/layout/svg/SVGUtils.cpp b/layout/svg/SVGUtils.cpp
new file mode 100644
index 0000000000..fe8f65800c
--- /dev/null
+++ b/layout/svg/SVGUtils.cpp
@@ -0,0 +1,1706 @@
+/* -*- 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 "SVGUtils.h"
+#include <algorithm>
+
+// Keep others in (case-insensitive) order:
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxMatrix.h"
+#include "gfxPlatform.h"
+#include "gfxRect.h"
+#include "gfxUtils.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsDisplayList.h"
+#include "nsFrameList.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsStyleStruct.h"
+#include "nsStyleTransformMatrix.h"
+#include "SVGAnimatedLength.h"
+#include "SVGPaintServerFrame.h"
+#include "nsTextFrame.h"
+#include "mozilla/CSSClipPathInstance.h"
+#include "mozilla/FilterInstance.h"
+#include "mozilla/ISVGDisplayableFrame.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_svg.h"
+#include "mozilla/SVGClipPathFrame.h"
+#include "mozilla/SVGContainerFrame.h"
+#include "mozilla/SVGContentUtils.h"
+#include "mozilla/SVGContextPaint.h"
+#include "mozilla/SVGForeignObjectFrame.h"
+#include "mozilla/SVGIntegrationUtils.h"
+#include "mozilla/SVGGeometryFrame.h"
+#include "mozilla/SVGMaskFrame.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGOuterSVGFrame.h"
+#include "mozilla/SVGTextFrame.h"
+#include "mozilla/Unused.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PatternHelpers.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/SVGClipPathElement.h"
+#include "mozilla/dom/SVGGeometryElement.h"
+#include "mozilla/dom/SVGPathElement.h"
+#include "mozilla/dom/SVGUnitTypesBinding.h"
+#include "mozilla/dom/SVGViewportElement.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::dom::SVGUnitTypes_Binding;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+bool NS_SVGDisplayListHitTestingEnabled() {
+ return mozilla::StaticPrefs::svg_display_lists_hit_testing_enabled();
+}
+
+bool NS_SVGDisplayListPaintingEnabled() {
+ return mozilla::StaticPrefs::svg_display_lists_painting_enabled();
+}
+
+bool NS_SVGNewGetBBoxEnabled() {
+ return mozilla::StaticPrefs::svg_new_getBBox_enabled();
+}
+
+namespace mozilla {
+
+// we only take the address of this:
+static gfx::UserDataKey sSVGAutoRenderStateKey;
+
+SVGAutoRenderState::SVGAutoRenderState(DrawTarget* aDrawTarget)
+ : mDrawTarget(aDrawTarget),
+ mOriginalRenderState(nullptr),
+ mPaintingToWindow(false) {
+ mOriginalRenderState = aDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey);
+ // We always remove ourselves from aContext before it dies, so
+ // passing nullptr as the destroy function is okay.
+ aDrawTarget->AddUserData(&sSVGAutoRenderStateKey, this, nullptr);
+}
+
+SVGAutoRenderState::~SVGAutoRenderState() {
+ mDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey);
+ if (mOriginalRenderState) {
+ mDrawTarget->AddUserData(&sSVGAutoRenderStateKey, mOriginalRenderState,
+ nullptr);
+ }
+}
+
+void SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow) {
+ mPaintingToWindow = aPaintingToWindow;
+}
+
+/* static */
+bool SVGAutoRenderState::IsPaintingToWindow(DrawTarget* aDrawTarget) {
+ void* state = aDrawTarget->GetUserData(&sSVGAutoRenderStateKey);
+ if (state) {
+ return static_cast<SVGAutoRenderState*>(state)->mPaintingToWindow;
+ }
+ return false;
+}
+
+nsRect SVGUtils::GetPostFilterInkOverflowRect(nsIFrame* aFrame,
+ const nsRect& aPreFilterRect) {
+ MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
+ "Called on invalid frame type");
+
+ // Note: we do not return here for eHasNoRefs since we must still handle any
+ // CSS filter functions.
+ // TODO: We currently pass nullptr instead of an nsTArray* here, but we
+ // actually should get the filter frames and then pass them into
+ // GetPostFilterBounds below! See bug 1494263.
+ // TODO: we should really return an empty rect for eHasRefsSomeInvalid since
+ // in that case we disable painting of the element.
+ if (!aFrame->StyleEffects()->HasFilters() ||
+ SVGObserverUtils::GetAndObserveFilters(aFrame, nullptr) ==
+ SVGObserverUtils::eHasRefsSomeInvalid) {
+ return aPreFilterRect;
+ }
+
+ return FilterInstance::GetPostFilterBounds(aFrame, nullptr, &aPreFilterRect)
+ .valueOr(aPreFilterRect);
+}
+
+bool SVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame* aFrame) {
+ return GetOuterSVGFrame(aFrame)->IsCallingReflowSVG();
+}
+
+bool SVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame) {
+ SVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame);
+ do {
+ if (outer->IsCallingReflowSVG()) {
+ return true;
+ }
+ outer = GetOuterSVGFrame(outer->GetParent());
+ } while (outer);
+ return false;
+}
+
+void SVGUtils::ScheduleReflowSVG(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG), "Passed bad frame!");
+
+ // If this is triggered, the callers should be fixed to call us before
+ // ReflowSVG is called. If we try to mark dirty bits on frames while we're
+ // in the process of removing them, things will get messed up.
+ MOZ_ASSERT(!OuterSVGIsCallingReflowSVG(aFrame),
+ "Do not call under ISVGDisplayableFrame::ReflowSVG!");
+
+ // We don't call SVGObserverUtils::InvalidateRenderingObservers here because
+ // we should only be called under InvalidateAndScheduleReflowSVG (which
+ // calls InvalidateBounds) or SVGDisplayContainerFrame::InsertFrames
+ // (at which point the frame has no observers).
+
+ if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ return;
+ }
+
+ if (aFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)) {
+ // Nothing to do if we're already dirty, or if the outer-<svg>
+ // hasn't yet had its initial reflow.
+ return;
+ }
+
+ SVGOuterSVGFrame* outerSVGFrame = nullptr;
+
+ // We must not add dirty bits to the SVGOuterSVGFrame or else
+ // PresShell::FrameNeedsReflow won't work when we pass it in below.
+ if (aFrame->IsSVGOuterSVGFrame()) {
+ outerSVGFrame = static_cast<SVGOuterSVGFrame*>(aFrame);
+ } else {
+ aFrame->MarkSubtreeDirty();
+
+ nsIFrame* f = aFrame->GetParent();
+ while (f && !f->IsSVGOuterSVGFrame()) {
+ if (f->HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) {
+ return;
+ }
+ f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+ f = f->GetParent();
+ MOZ_ASSERT(f->IsFrameOfType(nsIFrame::eSVG),
+ "IsSVGOuterSVGFrame check above not valid!");
+ }
+
+ outerSVGFrame = static_cast<SVGOuterSVGFrame*>(f);
+
+ MOZ_ASSERT(outerSVGFrame && outerSVGFrame->IsSVGOuterSVGFrame(),
+ "Did not find SVGOuterSVGFrame!");
+ }
+
+ if (outerSVGFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW)) {
+ // We're currently under an SVGOuterSVGFrame::Reflow call so there is no
+ // need to call PresShell::FrameNeedsReflow, since we have an
+ // SVGOuterSVGFrame::DidReflow call pending.
+ return;
+ }
+
+ nsFrameState dirtyBit =
+ (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY
+ : NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ aFrame->PresShell()->FrameNeedsReflow(outerSVGFrame, IntrinsicDirty::None,
+ dirtyBit);
+}
+
+bool SVGUtils::NeedsReflowSVG(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG),
+ "SVG uses bits differently!");
+
+ // The flags we test here may change, hence why we have this separate
+ // function.
+ return aFrame->IsSubtreeDirty();
+}
+
+Size SVGUtils::GetContextSize(const nsIFrame* aFrame) {
+ Size size;
+
+ MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
+ const SVGElement* element = static_cast<SVGElement*>(aFrame->GetContent());
+
+ SVGViewportElement* ctx = element->GetCtx();
+ if (ctx) {
+ size.width = ctx->GetLength(SVGContentUtils::X);
+ size.height = ctx->GetLength(SVGContentUtils::Y);
+ }
+ return size;
+}
+
+float SVGUtils::ObjectSpace(const gfxRect& aRect,
+ const SVGAnimatedLength* aLength) {
+ float axis;
+
+ switch (aLength->GetCtxType()) {
+ case SVGContentUtils::X:
+ axis = aRect.Width();
+ break;
+ case SVGContentUtils::Y:
+ axis = aRect.Height();
+ break;
+ case SVGContentUtils::XY:
+ axis = float(SVGContentUtils::ComputeNormalizedHypotenuse(
+ aRect.Width(), aRect.Height()));
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected ctx type");
+ axis = 0.0f;
+ break;
+ }
+ if (aLength->IsPercentage()) {
+ // Multiply first to avoid precision errors:
+ return axis * aLength->GetAnimValInSpecifiedUnits() / 100;
+ }
+ return aLength->GetAnimValue(static_cast<SVGViewportElement*>(nullptr)) *
+ axis;
+}
+
+float SVGUtils::UserSpace(SVGElement* aSVGElement,
+ const SVGAnimatedLength* aLength) {
+ return aLength->GetAnimValue(aSVGElement);
+}
+
+float SVGUtils::UserSpace(nsIFrame* aNonSVGContext,
+ const SVGAnimatedLength* aLength) {
+ return aLength->GetAnimValue(aNonSVGContext);
+}
+
+float SVGUtils::UserSpace(const UserSpaceMetrics& aMetrics,
+ const SVGAnimatedLength* aLength) {
+ return aLength->GetAnimValue(aMetrics);
+}
+
+SVGOuterSVGFrame* SVGUtils::GetOuterSVGFrame(nsIFrame* aFrame) {
+ while (aFrame) {
+ if (aFrame->IsSVGOuterSVGFrame()) {
+ return static_cast<SVGOuterSVGFrame*>(aFrame);
+ }
+ aFrame = aFrame->GetParent();
+ }
+
+ return nullptr;
+}
+
+nsIFrame* SVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame,
+ nsRect* aRect) {
+ ISVGDisplayableFrame* svg = do_QueryFrame(aFrame);
+ if (!svg) {
+ return nullptr;
+ }
+ SVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame);
+ if (outer == svg) {
+ return nullptr;
+ }
+
+ if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ *aRect = nsRect(0, 0, 0, 0);
+ } else {
+ uint32_t flags = SVGUtils::eForGetClientRects | SVGUtils::eBBoxIncludeFill |
+ SVGUtils::eBBoxIncludeStroke |
+ SVGUtils::eBBoxIncludeMarkers;
+
+ auto ctm = nsLayoutUtils::GetTransformToAncestor(RelativeTo{aFrame},
+ RelativeTo{outer});
+
+ float initPositionX = NSAppUnitsToFloatPixels(aFrame->GetPosition().x,
+ AppUnitsPerCSSPixel()),
+ initPositionY = NSAppUnitsToFloatPixels(aFrame->GetPosition().y,
+ AppUnitsPerCSSPixel());
+
+ Matrix mm;
+ ctm.ProjectTo2D();
+ ctm.CanDraw2D(&mm);
+ gfxMatrix m = ThebesMatrix(mm);
+
+ float appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+ float devPixelPerCSSPixel =
+ float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel;
+
+ // The matrix that GetBBox accepts should operate on "user space",
+ // i.e. with CSS pixel unit.
+ m = m.PreScale(devPixelPerCSSPixel, devPixelPerCSSPixel);
+
+ // Both SVGUtils::GetBBox and nsLayoutUtils::GetTransformToAncestor
+ // will count this displacement, we should remove it here to avoid
+ // double-counting.
+ m = m.PreTranslate(-initPositionX, -initPositionY);
+
+ gfxRect bbox = SVGUtils::GetBBox(aFrame, flags, &m);
+ *aRect = nsLayoutUtils::RoundGfxRectToAppRect(bbox, appUnitsPerDevPixel);
+ }
+
+ return outer;
+}
+
+gfxMatrix SVGUtils::GetCanvasTM(nsIFrame* aFrame) {
+ // XXX yuck, we really need a common interface for GetCanvasTM
+
+ if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
+ return GetCSSPxToDevPxMatrix(aFrame);
+ }
+
+ LayoutFrameType type = aFrame->Type();
+ if (type == LayoutFrameType::SVGForeignObject) {
+ return static_cast<SVGForeignObjectFrame*>(aFrame)->GetCanvasTM();
+ }
+ if (type == LayoutFrameType::SVGOuterSVG) {
+ return GetCSSPxToDevPxMatrix(aFrame);
+ }
+
+ SVGContainerFrame* containerFrame = do_QueryFrame(aFrame);
+ if (containerFrame) {
+ return containerFrame->GetCanvasTM();
+ }
+
+ return static_cast<SVGGeometryFrame*>(aFrame)->GetCanvasTM();
+}
+
+void SVGUtils::NotifyChildrenOfSVGChange(nsIFrame* aFrame, uint32_t aFlags) {
+ for (nsIFrame* kid : aFrame->PrincipalChildList()) {
+ ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
+ if (SVGFrame) {
+ SVGFrame->NotifySVGChanged(aFlags);
+ } else {
+ NS_ASSERTION(kid->IsFrameOfType(nsIFrame::eSVG) ||
+ SVGUtils::IsInSVGTextSubtree(kid),
+ "SVG frame expected");
+ // recurse into the children of container frames e.g. <clipPath>, <mask>
+ // in case they have child frames with transformation matrices
+ if (kid->IsFrameOfType(nsIFrame::eSVG)) {
+ NotifyChildrenOfSVGChange(kid, aFlags);
+ }
+ }
+ }
+}
+
+// ************************************************************
+
+float SVGUtils::ComputeOpacity(nsIFrame* aFrame, bool aHandleOpacity) {
+ float opacity = aFrame->StyleEffects()->mOpacity;
+
+ if (opacity != 1.0f &&
+ (SVGUtils::CanOptimizeOpacity(aFrame) || !aHandleOpacity)) {
+ return 1.0f;
+ }
+
+ return opacity;
+}
+
+void SVGUtils::DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity,
+ MaskUsage& aUsage) {
+ using ClipPathType = StyleClipPath::Tag;
+
+ aUsage.opacity = ComputeOpacity(aFrame, aHandleOpacity);
+
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+
+ const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset();
+
+ nsTArray<SVGMaskFrame*> maskFrames;
+ // XXX check return value?
+ SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
+ aUsage.shouldGenerateMaskLayer = (maskFrames.Length() > 0);
+
+ SVGClipPathFrame* clipPathFrame;
+ // XXX check return value?
+ SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
+ MOZ_ASSERT(!clipPathFrame || svgReset->mClipPath.IsUrl());
+
+ switch (svgReset->mClipPath.tag) {
+ case ClipPathType::Url:
+ if (clipPathFrame) {
+ if (clipPathFrame->IsTrivial()) {
+ aUsage.shouldApplyClipPath = true;
+ } else {
+ aUsage.shouldGenerateClipMaskLayer = true;
+ }
+ }
+ break;
+ case ClipPathType::Shape:
+ case ClipPathType::Box:
+ case ClipPathType::Path:
+ aUsage.shouldApplyBasicShapeOrPath = true;
+ break;
+ case ClipPathType::None:
+ MOZ_ASSERT(!aUsage.shouldGenerateClipMaskLayer &&
+ !aUsage.shouldApplyClipPath &&
+ !aUsage.shouldApplyBasicShapeOrPath);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type.");
+ break;
+ }
+}
+
+class MixModeBlender {
+ public:
+ using Factory = gfx::Factory;
+
+ MixModeBlender(nsIFrame* aFrame, gfxContext* aContext)
+ : mFrame(aFrame), mSourceCtx(aContext) {
+ MOZ_ASSERT(mFrame && mSourceCtx);
+ }
+
+ bool ShouldCreateDrawTargetForBlend() const {
+ return mFrame->StyleEffects()->mMixBlendMode != StyleBlend::Normal;
+ }
+
+ gfxContext* CreateBlendTarget(const gfxMatrix& aTransform) {
+ MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
+
+ // Create a temporary context to draw to so we can blend it back with
+ // another operator.
+ IntRect drawRect = ComputeClipExtsInDeviceSpace(aTransform);
+ if (drawRect.IsEmpty()) {
+ return nullptr;
+ }
+
+ RefPtr<DrawTarget> targetDT =
+ mSourceCtx->GetDrawTarget()->CreateSimilarDrawTarget(
+ drawRect.Size(), SurfaceFormat::B8G8R8A8);
+ if (!targetDT || !targetDT->IsValid()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(!mTargetCtx,
+ "CreateBlendTarget is designed to be used once only.");
+
+ mTargetCtx = gfxContext::CreateOrNull(targetDT);
+ MOZ_ASSERT(mTargetCtx); // already checked the draw target above
+ mTargetCtx->SetMatrix(mSourceCtx->CurrentMatrix() *
+ Matrix::Translation(-drawRect.TopLeft()));
+
+ mTargetOffset = drawRect.TopLeft();
+
+ return mTargetCtx;
+ }
+
+ void BlendToTarget() {
+ MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
+ MOZ_ASSERT(mTargetCtx,
+ "BlendToTarget should be used after CreateBlendTarget.");
+
+ RefPtr<SourceSurface> targetSurf = mTargetCtx->GetDrawTarget()->Snapshot();
+
+ gfxContextAutoSaveRestore save(mSourceCtx);
+ mSourceCtx->SetMatrix(Matrix()); // This will be restored right after.
+ RefPtr<gfxPattern> pattern = new gfxPattern(
+ targetSurf, Matrix::Translation(mTargetOffset.x, mTargetOffset.y));
+ mSourceCtx->SetPattern(pattern);
+ mSourceCtx->Paint();
+ }
+
+ private:
+ MixModeBlender() = delete;
+
+ IntRect ComputeClipExtsInDeviceSpace(const gfxMatrix& aTransform) {
+ // These are used if we require a temporary surface for a custom blend
+ // mode. Clip the source context first, so that we can generate a smaller
+ // temporary surface. (Since we will clip this context in
+ // SetupContextMatrix, a pair of save/restore is needed.)
+ gfxContextAutoSaveRestore saver;
+
+ if (!mFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ saver.SetContext(mSourceCtx);
+ // aFrame has a valid ink overflow rect, so clip to it before calling
+ // PushGroup() to minimize the size of the surfaces we'll composite:
+ gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(mSourceCtx);
+ mSourceCtx->Multiply(aTransform);
+ nsRect overflowRect = mFrame->InkOverflowRectRelativeToSelf();
+ if (mFrame->IsSVGGeometryFrameOrSubclass() ||
+ SVGUtils::IsInSVGTextSubtree(mFrame)) {
+ // Unlike containers, leaf frames do not include GetPosition() in
+ // GetCanvasTM().
+ overflowRect = overflowRect + mFrame->GetPosition();
+ }
+ mSourceCtx->Clip(NSRectToSnappedRect(
+ overflowRect, mFrame->PresContext()->AppUnitsPerDevPixel(),
+ *mSourceCtx->GetDrawTarget()));
+ }
+
+ // Get the clip extents in device space.
+ gfxRect clippedFrameSurfaceRect =
+ mSourceCtx->GetClipExtents(gfxContext::eDeviceSpace);
+ clippedFrameSurfaceRect.RoundOut();
+
+ IntRect result;
+ ToRect(clippedFrameSurfaceRect).ToIntRect(&result);
+
+ return Factory::CheckSurfaceSize(result.Size()) ? result : IntRect();
+ }
+
+ nsIFrame* mFrame;
+ gfxContext* mSourceCtx;
+ RefPtr<gfxContext> mTargetCtx;
+ IntPoint mTargetOffset;
+};
+
+void SVGUtils::PaintFrameWithEffects(nsIFrame* aFrame, gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect) {
+ NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
+ aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) ||
+ aFrame->PresContext()->Document()->IsSVGGlyphsDocument(),
+ "If display lists are enabled, only painting of non-display "
+ "SVG should take this code path");
+
+ ISVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame);
+ if (!svgFrame) {
+ return;
+ }
+
+ MaskUsage maskUsage;
+ DetermineMaskUsage(aFrame, true, maskUsage);
+ if (maskUsage.opacity == 0.0f) {
+ return;
+ }
+
+ if (auto* svg = SVGElement::FromNode(aFrame->GetContent())) {
+ if (!svg->HasValidDimensions()) {
+ return;
+ }
+ }
+
+ if (aDirtyRect && !aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ // Here we convert aFrame's paint bounds to outer-<svg> device space,
+ // compare it to aDirtyRect, and return early if they don't intersect.
+ // We don't do this optimization for nondisplay SVG since nondisplay
+ // SVG doesn't maintain bounds/overflow rects.
+ nsRect overflowRect = aFrame->InkOverflowRectRelativeToSelf();
+ if (aFrame->IsSVGGeometryFrameOrSubclass() ||
+ SVGUtils::IsInSVGTextSubtree(aFrame)) {
+ // Unlike containers, leaf frames do not include GetPosition() in
+ // GetCanvasTM().
+ overflowRect = overflowRect + aFrame->GetPosition();
+ }
+ int32_t appUnitsPerDevPx = aFrame->PresContext()->AppUnitsPerDevPixel();
+ gfxMatrix tm = aTransform;
+ if (aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
+ gfx::Matrix childrenOnlyTM;
+ if (static_cast<SVGContainerFrame*>(aFrame)->HasChildrenOnlyTransform(
+ &childrenOnlyTM)) {
+ // Undo the children-only transform:
+ if (!childrenOnlyTM.Invert()) {
+ return;
+ }
+ tm = ThebesMatrix(childrenOnlyTM) * tm;
+ }
+ }
+ nsIntRect bounds =
+ TransformFrameRectToOuterSVG(overflowRect, tm, aFrame->PresContext())
+ .ToOutsidePixels(appUnitsPerDevPx);
+ if (!aDirtyRect->Intersects(bounds)) {
+ return;
+ }
+ }
+
+ /* SVG defines the following rendering model:
+ *
+ * 1. Render fill
+ * 2. Render stroke
+ * 3. Render markers
+ * 4. Apply filter
+ * 5. Apply clipping, masking, group opacity
+ *
+ * We follow this, but perform a couple of optimizations:
+ *
+ * + Use cairo's clipPath when representable natively (single object
+ * clip region).
+ *f
+ * + Merge opacity and masking if both used together.
+ */
+
+ /* Properties are added lazily and may have been removed by a restyle,
+ so make sure all applicable ones are set again. */
+ SVGClipPathFrame* clipPathFrame;
+ nsTArray<SVGMaskFrame*> maskFrames;
+ // TODO: We currently pass nullptr instead of an nsTArray* here, but we
+ // actually should get the filter frames and then pass them into
+ // PaintFilteredFrame below! See bug 1494263.
+ const bool hasInvalidFilter =
+ SVGObserverUtils::GetAndObserveFilters(aFrame, nullptr) ==
+ SVGObserverUtils::eHasRefsSomeInvalid;
+ if (SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame) ==
+ SVGObserverUtils::eHasRefsSomeInvalid ||
+ SVGObserverUtils::GetAndObserveMasks(aFrame, &maskFrames) ==
+ SVGObserverUtils::eHasRefsSomeInvalid) {
+ // Some resource is invalid. We shouldn't paint anything.
+ return;
+ }
+
+ SVGMaskFrame* maskFrame = maskFrames.IsEmpty() ? nullptr : maskFrames[0];
+
+ MixModeBlender blender(aFrame, &aContext);
+ gfxContext* target = blender.ShouldCreateDrawTargetForBlend()
+ ? blender.CreateBlendTarget(aTransform)
+ : &aContext;
+
+ if (!target) {
+ return;
+ }
+
+ /* Check if we need to do additional operations on this child's
+ * rendering, which necessitates rendering into another surface. */
+ bool shouldGenerateMask =
+ (maskUsage.opacity != 1.0f || maskUsage.shouldGenerateClipMaskLayer ||
+ maskUsage.shouldGenerateMaskLayer);
+ bool shouldPushMask = false;
+
+ if (shouldGenerateMask) {
+ RefPtr<SourceSurface> maskSurface;
+
+ // maskFrame can be nullptr even if maskUsage.shouldGenerateMaskLayer is
+ // true. That happens when a user gives an unresolvable mask-id, such as
+ // mask:url()
+ // mask:url(#id-which-does-not-exist)
+ // Since we only uses SVGUtils with SVG elements, not like mask on an
+ // HTML element, we should treat an unresolvable mask as no-mask here.
+ if (maskUsage.shouldGenerateMaskLayer && maskFrame) {
+ StyleMaskMode maskMode =
+ aFrame->StyleSVGReset()->mMask.mLayers[0].mMaskMode;
+ SVGMaskFrame::MaskParams params(aContext.GetDrawTarget(), aFrame,
+ aTransform, maskUsage.opacity, maskMode,
+ aImgParams);
+
+ maskSurface = maskFrame->GetMaskForMaskedFrame(params);
+
+ if (!maskSurface) {
+ // Either entire surface is clipped out, or gfx buffer allocation
+ // failure in SVGMaskFrame::GetMaskForMaskedFrame.
+ return;
+ }
+ shouldPushMask = true;
+ }
+
+ if (maskUsage.shouldGenerateClipMaskLayer) {
+ RefPtr<SourceSurface> clipMaskSurface =
+ clipPathFrame->GetClipMask(aContext, aFrame, aTransform, maskSurface);
+ if (clipMaskSurface) {
+ maskSurface = clipMaskSurface;
+ } else {
+ // Either entire surface is clipped out, or gfx buffer allocation
+ // failure in SVGClipPathFrame::GetClipMask.
+ return;
+ }
+ shouldPushMask = true;
+ }
+
+ if (!maskUsage.shouldGenerateClipMaskLayer &&
+ !maskUsage.shouldGenerateMaskLayer) {
+ shouldPushMask = true;
+ }
+
+ // SVG mask multiply opacity into maskSurface already, so we do not bother
+ // to apply opacity again.
+ if (shouldPushMask) {
+ // We want the mask to be untransformed so use the inverse of the
+ // current transform as the maskTransform to compensate.
+ Matrix maskTransform = aContext.CurrentMatrix();
+ maskTransform.Invert();
+ target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
+ maskFrame ? 1.0 : maskUsage.opacity,
+ maskSurface, maskTransform);
+ }
+ }
+
+ /* If this frame has only a trivial clipPath, set up cairo's clipping now so
+ * we can just do normal painting and get it clipped appropriately.
+ */
+ if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
+ if (maskUsage.shouldApplyClipPath) {
+ clipPathFrame->ApplyClipPath(aContext, aFrame, aTransform);
+ } else {
+ CSSClipPathInstance::ApplyBasicShapeOrPathClip(aContext, aFrame,
+ aTransform);
+ }
+ }
+
+ /* Paint the child */
+
+ // Invalid filters should render the unfiltered contents per spec.
+ if (aFrame->StyleEffects()->HasFilters() && !hasInvalidFilter) {
+ nsRegion* dirtyRegion = nullptr;
+ nsRegion tmpDirtyRegion;
+ if (aDirtyRect) {
+ // aDirtyRect is in outer-<svg> device pixels, but the filter code needs
+ // it in frame space.
+ gfxMatrix userToDeviceSpace = aTransform;
+ if (userToDeviceSpace.IsSingular()) {
+ return;
+ }
+ gfxMatrix deviceToUserSpace = userToDeviceSpace;
+ deviceToUserSpace.Invert();
+ gfxRect dirtyBounds = deviceToUserSpace.TransformBounds(gfxRect(
+ aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, aDirtyRect->height));
+ tmpDirtyRegion = nsLayoutUtils::RoundGfxRectToAppRect(
+ dirtyBounds, AppUnitsPerCSSPixel()) -
+ aFrame->GetPosition();
+ dirtyRegion = &tmpDirtyRegion;
+ }
+
+ gfxContextMatrixAutoSaveRestore autoSR(target);
+
+ // 'target' is currently scaled such that its user space units are CSS
+ // pixels (SVG user space units). But PaintFilteredFrame expects it to be
+ // scaled in such a way that its user space units are device pixels. So we
+ // have to adjust the scale.
+ gfxMatrix reverseScaleMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aFrame);
+ DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
+ target->SetMatrixDouble(reverseScaleMatrix * aTransform *
+ target->CurrentMatrixDouble());
+
+ auto callback = [&](gfxContext& aContext, imgDrawingParams& aImgParams,
+ const gfxMatrix* aFilterTransform,
+ const nsIntRect* aDirtyRect) {
+ nsIntRect* dirtyRect = nullptr;
+ nsIntRect tmpDirtyRect;
+
+ // aDirtyRect is in user-space pixels, we need to convert to
+ // outer-SVG-frame-relative device pixels.
+ if (aDirtyRect) {
+ MOZ_ASSERT(aFilterTransform);
+ gfxMatrix userToDeviceSpace = *aFilterTransform;
+ if (userToDeviceSpace.IsSingular()) {
+ return;
+ }
+ gfxRect dirtyBounds = userToDeviceSpace.TransformBounds(
+ gfxRect(aDirtyRect->x, aDirtyRect->y, aDirtyRect->width,
+ aDirtyRect->height));
+ dirtyBounds.RoundOut();
+ if (gfxUtils::GfxRectToIntRect(dirtyBounds, &tmpDirtyRect)) {
+ dirtyRect = &tmpDirtyRect;
+ }
+ }
+
+ svgFrame->PaintSVG(aContext,
+ aFilterTransform
+ ? SVGUtils::GetCSSPxToDevPxMatrix(aFrame)
+ : aTransform,
+ aImgParams, aFilterTransform ? dirtyRect : aDirtyRect);
+ };
+ FilterInstance::PaintFilteredFrame(
+ aFrame, aFrame->StyleEffects()->mFilters.AsSpan(), target, callback,
+ dirtyRegion, aImgParams);
+ } else {
+ svgFrame->PaintSVG(*target, aTransform, aImgParams, aDirtyRect);
+ }
+
+ if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
+ aContext.PopClip();
+ }
+
+ if (shouldPushMask) {
+ target->PopGroupAndBlend();
+ }
+
+ if (blender.ShouldCreateDrawTargetForBlend()) {
+ MOZ_ASSERT(target != &aContext);
+ blender.BlendToTarget();
+ }
+}
+
+bool SVGUtils::HitTestClip(nsIFrame* aFrame, const gfxPoint& aPoint) {
+ const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset();
+ if (!svgReset->HasClipPath()) {
+ return true;
+ }
+ if (svgReset->mClipPath.IsUrl()) {
+ // If the clip-path property references non-existent or invalid clipPath
+ // element(s) we ignore it.
+ SVGClipPathFrame* clipPathFrame;
+ SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame);
+ return !clipPathFrame ||
+ clipPathFrame->PointIsInsideClipPath(aFrame, aPoint);
+ }
+ return CSSClipPathInstance::HitTestBasicShapeOrPathClip(aFrame, aPoint);
+}
+
+nsIFrame* SVGUtils::HitTestChildren(SVGDisplayContainerFrame* aFrame,
+ const gfxPoint& aPoint) {
+ // First we transform aPoint into the coordinate space established by aFrame
+ // for its children (e.g. take account of any 'viewBox' attribute):
+ gfxPoint point = aPoint;
+ if (auto* svg = SVGElement::FromNode(aFrame->GetContent())) {
+ gfxMatrix m = svg->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace);
+ if (!m.IsIdentity()) {
+ if (!m.Invert()) {
+ return nullptr;
+ }
+ point = m.TransformPoint(point);
+ }
+ }
+
+ // Traverse the list in reverse order, so that if we get a hit we know that's
+ // the topmost frame that intersects the point; then we can just return it.
+ nsIFrame* result = nullptr;
+ for (nsIFrame* current = aFrame->PrincipalChildList().LastChild(); current;
+ current = current->GetPrevSibling()) {
+ ISVGDisplayableFrame* SVGFrame = do_QueryFrame(current);
+ if (SVGFrame) {
+ const nsIContent* content = current->GetContent();
+ if (auto* svg = SVGElement::FromNode(content)) {
+ if (!svg->HasValidDimensions()) {
+ continue;
+ }
+ }
+ // GetFrameForPoint() expects a point in its frame's SVG user space, so
+ // we need to convert to that space:
+ gfxPoint p = point;
+ if (auto* svg = SVGElement::FromNode(content)) {
+ gfxMatrix m =
+ svg->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent);
+ if (!m.IsIdentity()) {
+ if (!m.Invert()) {
+ continue;
+ }
+ p = m.TransformPoint(p);
+ }
+ }
+ result = SVGFrame->GetFrameForPoint(p);
+ if (result) break;
+ }
+ }
+
+ if (result && !HitTestClip(aFrame, aPoint)) result = nullptr;
+
+ return result;
+}
+
+nsRect SVGUtils::TransformFrameRectToOuterSVG(const nsRect& aRect,
+ const gfxMatrix& aMatrix,
+ nsPresContext* aPresContext) {
+ gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height);
+ r.Scale(1.0 / AppUnitsPerCSSPixel());
+ return nsLayoutUtils::RoundGfxRectToAppRect(
+ aMatrix.TransformBounds(r), aPresContext->AppUnitsPerDevPixel());
+}
+
+IntSize SVGUtils::ConvertToSurfaceSize(const gfxSize& aSize,
+ bool* aResultOverflows) {
+ IntSize surfaceSize(ClampToInt(ceil(aSize.width)),
+ ClampToInt(ceil(aSize.height)));
+
+ *aResultOverflows = surfaceSize.width != ceil(aSize.width) ||
+ surfaceSize.height != ceil(aSize.height);
+
+ if (!Factory::AllowedSurfaceSize(surfaceSize)) {
+ surfaceSize.width =
+ std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, surfaceSize.width);
+ surfaceSize.height =
+ std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, surfaceSize.height);
+ *aResultOverflows = true;
+ }
+
+ return surfaceSize;
+}
+
+bool SVGUtils::HitTestRect(const gfx::Matrix& aMatrix, float aRX, float aRY,
+ float aRWidth, float aRHeight, float aX, float aY) {
+ gfx::Rect rect(aRX, aRY, aRWidth, aRHeight);
+ if (rect.IsEmpty() || aMatrix.IsSingular()) {
+ return false;
+ }
+ gfx::Matrix toRectSpace = aMatrix;
+ toRectSpace.Invert();
+ gfx::Point p = toRectSpace.TransformPoint(gfx::Point(aX, aY));
+ return rect.x <= p.x && p.x <= rect.XMost() && rect.y <= p.y &&
+ p.y <= rect.YMost();
+}
+
+gfxRect SVGUtils::GetClipRectForFrame(nsIFrame* aFrame, float aX, float aY,
+ float aWidth, float aHeight) {
+ const nsStyleDisplay* disp = aFrame->StyleDisplay();
+ const nsStyleEffects* effects = aFrame->StyleEffects();
+
+ bool clipApplies = disp->mOverflowX == StyleOverflow::Hidden ||
+ disp->mOverflowY == StyleOverflow::Hidden;
+
+ if (!clipApplies || effects->mClip.IsAuto()) {
+ return gfxRect(aX, aY, aWidth, aHeight);
+ }
+
+ const auto& rect = effects->mClip.AsRect();
+ nsRect coordClipRect = rect.ToLayoutRect();
+ nsIntRect clipPxRect = coordClipRect.ToOutsidePixels(
+ aFrame->PresContext()->AppUnitsPerDevPixel());
+ gfxRect clipRect =
+ gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width, clipPxRect.height);
+ if (rect.right.IsAuto()) {
+ clipRect.width = aWidth - clipRect.X();
+ }
+ if (rect.bottom.IsAuto()) {
+ clipRect.height = aHeight - clipRect.Y();
+ }
+ if (disp->mOverflowX != StyleOverflow::Hidden) {
+ clipRect.x = aX;
+ clipRect.width = aWidth;
+ }
+ if (disp->mOverflowY != StyleOverflow::Hidden) {
+ clipRect.y = aY;
+ clipRect.height = aHeight;
+ }
+ return clipRect;
+}
+
+void SVGUtils::SetClipRect(gfxContext* aContext, const gfxMatrix& aCTM,
+ const gfxRect& aRect) {
+ if (aCTM.IsSingular()) {
+ return;
+ }
+
+ gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(aContext);
+ aContext->Multiply(aCTM);
+ aContext->Clip(aRect);
+}
+
+gfxRect SVGUtils::GetBBox(nsIFrame* aFrame, uint32_t aFlags,
+ const gfxMatrix* aToBoundsSpace) {
+ if (aFrame->IsTextFrame()) {
+ aFrame = aFrame->GetParent();
+ }
+
+ if (SVGUtils::IsInSVGTextSubtree(aFrame)) {
+ // It is possible to apply a gradient, pattern, clipping path, mask or
+ // filter to text. When one of these facilities is applied to text
+ // the bounding box is the entire text element in all
+ // cases.
+ nsIFrame* ancestor = GetFirstNonAAncestorFrame(aFrame);
+ if (ancestor && SVGUtils::IsInSVGTextSubtree(ancestor)) {
+ while (!ancestor->IsSVGTextFrame()) {
+ ancestor = ancestor->GetParent();
+ }
+ }
+ aFrame = ancestor;
+ }
+
+ ISVGDisplayableFrame* svg = do_QueryFrame(aFrame);
+ const bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
+ if (hasSVGLayout && !svg) {
+ // An SVG frame, but not one that can be displayed directly (for
+ // example, nsGradientFrame). These can't contribute to the bbox.
+ return gfxRect();
+ }
+
+ const bool isOuterSVG = svg && !hasSVGLayout;
+ MOZ_ASSERT(!isOuterSVG || aFrame->IsSVGOuterSVGFrame());
+ if (!svg || (isOuterSVG && (aFlags & eUseFrameBoundsForOuterSVG))) {
+ // An HTML element or an SVG outer frame.
+ MOZ_ASSERT(!hasSVGLayout);
+ bool onlyCurrentFrame = aFlags & eIncludeOnlyCurrentFrameForNonSVGElement;
+ return SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(
+ aFrame,
+ /* aUnionContinuations = */ !onlyCurrentFrame);
+ }
+
+ MOZ_ASSERT(svg);
+
+ if (auto* element = SVGElement::FromNodeOrNull(aFrame->GetContent())) {
+ if (!element->HasValidDimensions()) {
+ return gfxRect();
+ }
+ }
+
+ // Clean out flags which have no effects on returning bbox from now, so that
+ // we can cache and reuse ObjectBoundingBoxProperty() in the code below.
+ aFlags &= ~eIncludeOnlyCurrentFrameForNonSVGElement;
+ aFlags &= ~eUseFrameBoundsForOuterSVG;
+ if (!aFrame->IsSVGUseFrame()) {
+ aFlags &= ~eUseUserSpaceOfUseElement;
+ }
+
+ if (aFlags == eBBoxIncludeFillGeometry &&
+ // We only cache bbox in element's own user space
+ !aToBoundsSpace) {
+ gfxRect* prop = aFrame->GetProperty(ObjectBoundingBoxProperty());
+ if (prop) {
+ return *prop;
+ }
+ }
+
+ gfxMatrix matrix;
+ if (aToBoundsSpace) {
+ matrix = *aToBoundsSpace;
+ }
+
+ if (aFrame->IsSVGForeignObjectFrame() ||
+ aFlags & SVGUtils::eUseUserSpaceOfUseElement) {
+ // The spec says getBBox "Returns the tight bounding box in *current user
+ // space*". So we should really be doing this for all elements, but that
+ // needs investigation to check that we won't break too much content.
+ // NOTE: When changing this to apply to other frame types, make sure to
+ // also update SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset.
+ MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
+ SVGElement* element = static_cast<SVGElement*>(aFrame->GetContent());
+ matrix = element->PrependLocalTransformsTo(matrix, eChildToUserSpace);
+ }
+ gfxRect bbox =
+ svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect();
+ // Account for 'clipped'.
+ if (aFlags & SVGUtils::eBBoxIncludeClipped) {
+ gfxRect clipRect(0, 0, 0, 0);
+ float x, y, width, height;
+ gfxMatrix tm;
+ gfxRect fillBBox =
+ svg->GetBBoxContribution(ToMatrix(tm), SVGUtils::eBBoxIncludeFill)
+ .ToThebesRect();
+ x = fillBBox.x;
+ y = fillBBox.y;
+ width = fillBBox.width;
+ height = fillBBox.height;
+ bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow();
+ if (hasClip) {
+ clipRect = SVGUtils::GetClipRectForFrame(aFrame, x, y, width, height);
+ if (aFrame->IsSVGForeignObjectFrame() || aFrame->IsSVGUseFrame()) {
+ clipRect = matrix.TransformBounds(clipRect);
+ }
+ }
+ SVGClipPathFrame* clipPathFrame;
+ if (SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame) ==
+ SVGObserverUtils::eHasRefsSomeInvalid) {
+ bbox = gfxRect(0, 0, 0, 0);
+ } else {
+ if (clipPathFrame) {
+ SVGClipPathElement* clipContent =
+ static_cast<SVGClipPathElement*>(clipPathFrame->GetContent());
+ if (clipContent->IsUnitsObjectBoundingBox()) {
+ matrix.PreTranslate(gfxPoint(x, y));
+ matrix.PreScale(width, height);
+ } else if (aFrame->IsSVGForeignObjectFrame()) {
+ matrix = gfxMatrix();
+ }
+
+ matrix =
+ SVGUtils::GetTransformMatrixInUserSpace(clipPathFrame) * matrix;
+
+ bbox = clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix, aFlags)
+ .ToThebesRect();
+ }
+
+ if (hasClip) {
+ bbox = bbox.Intersect(clipRect);
+ }
+
+ if (bbox.IsEmpty()) {
+ bbox = gfxRect(0, 0, 0, 0);
+ }
+ }
+ }
+
+ if (aFlags == eBBoxIncludeFillGeometry &&
+ // We only cache bbox in element's own user space
+ !aToBoundsSpace) {
+ // Obtaining the bbox for objectBoundingBox calculations is common so we
+ // cache the result for future calls, since calculation can be expensive:
+ aFrame->SetProperty(ObjectBoundingBoxProperty(), new gfxRect(bbox));
+ }
+
+ return bbox;
+}
+
+gfxPoint SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame* aFrame) {
+ if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ // The user space for non-SVG frames is defined as the bounding box of the
+ // frame's border-box rects over all continuations.
+ return gfxPoint();
+ }
+
+ // Leaf frames apply their own offset inside their user space.
+ if (aFrame->IsSVGGeometryFrameOrSubclass() ||
+ SVGUtils::IsInSVGTextSubtree(aFrame)) {
+ return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(),
+ AppUnitsPerCSSPixel())
+ .TopLeft();
+ }
+
+ // For foreignObject frames, SVGUtils::GetBBox applies their local
+ // transform, so we need to do the same here.
+ if (aFrame->IsSVGForeignObjectFrame()) {
+ gfxMatrix transform =
+ static_cast<SVGElement*>(aFrame->GetContent())
+ ->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace);
+ NS_ASSERTION(!transform.HasNonTranslation(),
+ "we're relying on this being an offset-only transform");
+ return transform.GetTranslation();
+ }
+
+ return gfxPoint();
+}
+
+static gfxRect GetBoundingBoxRelativeRect(const SVGAnimatedLength* aXYWH,
+ const gfxRect& aBBox) {
+ return gfxRect(aBBox.x + SVGUtils::ObjectSpace(aBBox, &aXYWH[0]),
+ aBBox.y + SVGUtils::ObjectSpace(aBBox, &aXYWH[1]),
+ SVGUtils::ObjectSpace(aBBox, &aXYWH[2]),
+ SVGUtils::ObjectSpace(aBBox, &aXYWH[3]));
+}
+
+gfxRect SVGUtils::GetRelativeRect(uint16_t aUnits,
+ const SVGAnimatedLength* aXYWH,
+ const gfxRect& aBBox,
+ const UserSpaceMetrics& aMetrics) {
+ if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ return GetBoundingBoxRelativeRect(aXYWH, aBBox);
+ }
+ return gfxRect(UserSpace(aMetrics, &aXYWH[0]), UserSpace(aMetrics, &aXYWH[1]),
+ UserSpace(aMetrics, &aXYWH[2]),
+ UserSpace(aMetrics, &aXYWH[3]));
+}
+
+gfxRect SVGUtils::GetRelativeRect(uint16_t aUnits,
+ const SVGAnimatedLength* aXYWH,
+ const gfxRect& aBBox, nsIFrame* aFrame) {
+ if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ return GetBoundingBoxRelativeRect(aXYWH, aBBox);
+ }
+ if (SVGElement* svgElement = SVGElement::FromNode(aFrame->GetContent())) {
+ return GetRelativeRect(aUnits, aXYWH, aBBox, SVGElementMetrics(svgElement));
+ }
+ return GetRelativeRect(aUnits, aXYWH, aBBox,
+ NonSVGFrameUserSpaceMetrics(aFrame));
+}
+
+bool SVGUtils::CanOptimizeOpacity(nsIFrame* aFrame) {
+ if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ return false;
+ }
+ LayoutFrameType type = aFrame->Type();
+ if (type != LayoutFrameType::SVGImage &&
+ type != LayoutFrameType::SVGGeometry) {
+ return false;
+ }
+ if (aFrame->StyleEffects()->HasFilters()) {
+ return false;
+ }
+ // XXX The SVG WG is intending to allow fill, stroke and markers on <image>
+ if (type == LayoutFrameType::SVGImage) {
+ return true;
+ }
+ const nsStyleSVG* style = aFrame->StyleSVG();
+ if (style->HasMarker()) {
+ return false;
+ }
+
+ if (nsLayoutUtils::HasAnimationOfPropertySet(
+ aFrame, nsCSSPropertyIDSet::OpacityProperties())) {
+ return false;
+ }
+
+ return !style->HasFill() || !HasStroke(aFrame);
+}
+
+gfxMatrix SVGUtils::AdjustMatrixForUnits(const gfxMatrix& aMatrix,
+ SVGAnimatedEnumeration* aUnits,
+ nsIFrame* aFrame, uint32_t aFlags) {
+ if (aFrame && aUnits->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ gfxRect bbox = GetBBox(aFrame, aFlags);
+ gfxMatrix tm = aMatrix;
+ tm.PreTranslate(gfxPoint(bbox.X(), bbox.Y()));
+ tm.PreScale(bbox.Width(), bbox.Height());
+ return tm;
+ }
+ return aMatrix;
+}
+
+nsIFrame* SVGUtils::GetFirstNonAAncestorFrame(nsIFrame* aStartFrame) {
+ for (nsIFrame* ancestorFrame = aStartFrame; ancestorFrame;
+ ancestorFrame = ancestorFrame->GetParent()) {
+ if (!ancestorFrame->IsSVGAFrame()) {
+ return ancestorFrame;
+ }
+ }
+ return nullptr;
+}
+
+bool SVGUtils::GetNonScalingStrokeTransform(nsIFrame* aFrame,
+ gfxMatrix* aUserToOuterSVG) {
+ if (aFrame->GetContent()->IsText()) {
+ aFrame = aFrame->GetParent();
+ }
+
+ if (!aFrame->StyleSVGReset()->HasNonScalingStroke()) {
+ return false;
+ }
+
+ MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "should be an SVG element");
+
+ *aUserToOuterSVG = ThebesMatrix(SVGContentUtils::GetCTM(
+ static_cast<SVGElement*>(aFrame->GetContent()), true));
+
+ return aUserToOuterSVG->HasNonTranslation();
+}
+
+// The logic here comes from _cairo_stroke_style_max_distance_from_path
+static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
+ nsIFrame* aFrame,
+ double aStyleExpansionFactor,
+ const gfxMatrix& aMatrix) {
+ double style_expansion =
+ aStyleExpansionFactor * SVGUtils::GetStrokeWidth(aFrame);
+
+ gfxMatrix matrix = aMatrix;
+
+ gfxMatrix outerSVGToUser;
+ if (SVGUtils::GetNonScalingStrokeTransform(aFrame, &outerSVGToUser)) {
+ outerSVGToUser.Invert();
+ matrix.PreMultiply(outerSVGToUser);
+ }
+
+ double dx = style_expansion * (fabs(matrix._11) + fabs(matrix._21));
+ double dy = style_expansion * (fabs(matrix._22) + fabs(matrix._12));
+
+ gfxRect strokeExtents = aPathExtents;
+ strokeExtents.Inflate(dx, dy);
+ return strokeExtents;
+}
+
+/*static*/
+gfxRect SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
+ nsTextFrame* aFrame,
+ const gfxMatrix& aMatrix) {
+ NS_ASSERTION(SVGUtils::IsInSVGTextSubtree(aFrame),
+ "expected an nsTextFrame for SVG text");
+ return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5,
+ aMatrix);
+}
+
+/*static*/
+gfxRect SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
+ SVGGeometryFrame* aFrame,
+ const gfxMatrix& aMatrix) {
+ bool strokeMayHaveCorners =
+ !SVGContentUtils::ShapeTypeHasNoCorners(aFrame->GetContent());
+
+ // For a shape without corners the stroke can only extend half the stroke
+ // width from the path in the x/y-axis directions. For shapes with corners
+ // the stroke can extend by sqrt(1/2) (think 45 degree rotated rect, or line
+ // with stroke-linecaps="square").
+ double styleExpansionFactor = strokeMayHaveCorners ? M_SQRT1_2 : 0.5;
+
+ // The stroke can extend even further for paths that can be affected by
+ // stroke-miterlimit.
+ // We only need to do this if the limit is greater than 1, but it's probably
+ // not worth optimizing for that.
+ bool affectedByMiterlimit = aFrame->GetContent()->IsAnyOfSVGElements(
+ nsGkAtoms::path, nsGkAtoms::polyline, nsGkAtoms::polygon);
+
+ if (affectedByMiterlimit) {
+ const nsStyleSVG* style = aFrame->StyleSVG();
+ if (style->mStrokeLinejoin == StyleStrokeLinejoin::Miter &&
+ styleExpansionFactor < style->mStrokeMiterlimit / 2.0) {
+ styleExpansionFactor = style->mStrokeMiterlimit / 2.0;
+ }
+ }
+
+ return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame,
+ styleExpansionFactor, aMatrix);
+}
+
+// ----------------------------------------------------------------------
+
+/* static */
+nscolor SVGUtils::GetFallbackOrPaintColor(
+ const ComputedStyle& aStyle, StyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ nscolor aDefaultContextFallbackColor) {
+ const auto& paint = aStyle.StyleSVG()->*aFillOrStroke;
+ nscolor color;
+ switch (paint.kind.tag) {
+ case StyleSVGPaintKind::Tag::PaintServer:
+ color = paint.fallback.IsColor()
+ ? paint.fallback.AsColor().CalcColor(aStyle)
+ : NS_RGBA(0, 0, 0, 0);
+ break;
+ case StyleSVGPaintKind::Tag::ContextStroke:
+ case StyleSVGPaintKind::Tag::ContextFill:
+ color = paint.fallback.IsColor()
+ ? paint.fallback.AsColor().CalcColor(aStyle)
+ : aDefaultContextFallbackColor;
+ break;
+ default:
+ color = paint.kind.AsColor().CalcColor(aStyle);
+ break;
+ }
+ if (const auto* styleIfVisited = aStyle.GetStyleIfVisited()) {
+ const auto& paintIfVisited = styleIfVisited->StyleSVG()->*aFillOrStroke;
+ // To prevent Web content from detecting if a user has visited a URL
+ // (via URL loading triggered by paint servers or performance
+ // differences between paint servers or between a paint server and a
+ // color), we do not allow whether links are visited to change which
+ // paint server is used or switch between paint servers and simple
+ // colors. A :visited style may only override a simple color with
+ // another simple color.
+ if (paintIfVisited.kind.IsColor() && paint.kind.IsColor()) {
+ nscolor colors[2] = {
+ color, paintIfVisited.kind.AsColor().CalcColor(*styleIfVisited)};
+ return ComputedStyle::CombineVisitedColors(colors,
+ aStyle.RelevantLinkVisited());
+ }
+ }
+ return color;
+}
+
+/* static */
+void SVGUtils::MakeFillPatternFor(nsIFrame* aFrame, gfxContext* aContext,
+ GeneralPattern* aOutPattern,
+ imgDrawingParams& aImgParams,
+ SVGContextPaint* aContextPaint) {
+ const nsStyleSVG* style = aFrame->StyleSVG();
+ if (style->mFill.kind.IsNone()) {
+ return;
+ }
+
+ const float opacity = aFrame->StyleEffects()->mOpacity;
+
+ float fillOpacity = GetOpacity(style->mFillOpacity, aContextPaint);
+ if (opacity < 1.0f && SVGUtils::CanOptimizeOpacity(aFrame)) {
+ // Combine the group opacity into the fill opacity (we will have skipped
+ // creating an offscreen surface to apply the group opacity).
+ fillOpacity *= opacity;
+ }
+
+ const DrawTarget* dt = aContext->GetDrawTarget();
+
+ SVGPaintServerFrame* ps =
+ SVGObserverUtils::GetAndObservePaintServer(aFrame, &nsStyleSVG::mFill);
+
+ if (ps) {
+ RefPtr<gfxPattern> pattern =
+ ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrixDouble(),
+ &nsStyleSVG::mFill, fillOpacity, aImgParams);
+ if (pattern) {
+ pattern->CacheColorStops(dt);
+ aOutPattern->Init(*pattern->GetPattern(dt));
+ return;
+ }
+ }
+
+ if (aContextPaint) {
+ RefPtr<gfxPattern> pattern;
+ switch (style->mFill.kind.tag) {
+ case StyleSVGPaintKind::Tag::ContextFill:
+ pattern = aContextPaint->GetFillPattern(
+ dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams);
+ break;
+ case StyleSVGPaintKind::Tag::ContextStroke:
+ pattern = aContextPaint->GetStrokePattern(
+ dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams);
+ break;
+ default:;
+ }
+ if (pattern) {
+ aOutPattern->Init(*pattern->GetPattern(dt));
+ return;
+ }
+ }
+
+ if (style->mFill.fallback.IsNone()) {
+ return;
+ }
+
+ // On failure, use the fallback colour in case we have an
+ // objectBoundingBox where the width or height of the object is zero.
+ // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
+ sRGBColor color(sRGBColor::FromABGR(GetFallbackOrPaintColor(
+ *aFrame->Style(), &nsStyleSVG::mFill, NS_RGB(0, 0, 0))));
+ color.a *= fillOpacity;
+ aOutPattern->InitColorPattern(ToDeviceColor(color));
+}
+
+/* static */
+void SVGUtils::MakeStrokePatternFor(nsIFrame* aFrame, gfxContext* aContext,
+ GeneralPattern* aOutPattern,
+ imgDrawingParams& aImgParams,
+ SVGContextPaint* aContextPaint) {
+ const nsStyleSVG* style = aFrame->StyleSVG();
+ if (style->mStroke.kind.IsNone()) {
+ return;
+ }
+
+ const float opacity = aFrame->StyleEffects()->mOpacity;
+
+ float strokeOpacity = GetOpacity(style->mStrokeOpacity, aContextPaint);
+ if (opacity < 1.0f && SVGUtils::CanOptimizeOpacity(aFrame)) {
+ // Combine the group opacity into the stroke opacity (we will have skipped
+ // creating an offscreen surface to apply the group opacity).
+ strokeOpacity *= opacity;
+ }
+
+ const DrawTarget* dt = aContext->GetDrawTarget();
+
+ SVGPaintServerFrame* ps =
+ SVGObserverUtils::GetAndObservePaintServer(aFrame, &nsStyleSVG::mStroke);
+
+ if (ps) {
+ RefPtr<gfxPattern> pattern = ps->GetPaintServerPattern(
+ aFrame, dt, aContext->CurrentMatrixDouble(), &nsStyleSVG::mStroke,
+ strokeOpacity, aImgParams);
+ if (pattern) {
+ pattern->CacheColorStops(dt);
+ aOutPattern->Init(*pattern->GetPattern(dt));
+ return;
+ }
+ }
+
+ if (aContextPaint) {
+ RefPtr<gfxPattern> pattern;
+ switch (style->mStroke.kind.tag) {
+ case StyleSVGPaintKind::Tag::ContextFill:
+ pattern = aContextPaint->GetFillPattern(
+ dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams);
+ break;
+ case StyleSVGPaintKind::Tag::ContextStroke:
+ pattern = aContextPaint->GetStrokePattern(
+ dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams);
+ break;
+ default:;
+ }
+ if (pattern) {
+ aOutPattern->Init(*pattern->GetPattern(dt));
+ return;
+ }
+ }
+
+ if (style->mStroke.fallback.IsNone()) {
+ return;
+ }
+
+ // On failure, use the fallback colour in case we have an
+ // objectBoundingBox where the width or height of the object is zero.
+ // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
+ sRGBColor color(sRGBColor::FromABGR(GetFallbackOrPaintColor(
+ *aFrame->Style(), &nsStyleSVG::mStroke, NS_RGBA(0, 0, 0, 0))));
+ color.a *= strokeOpacity;
+ aOutPattern->InitColorPattern(ToDeviceColor(color));
+}
+
+/* static */
+float SVGUtils::GetOpacity(const StyleSVGOpacity& aOpacity,
+ SVGContextPaint* aContextPaint) {
+ float opacity = 1.0f;
+ switch (aOpacity.tag) {
+ case StyleSVGOpacity::Tag::Opacity:
+ return aOpacity.AsOpacity();
+ case StyleSVGOpacity::Tag::ContextFillOpacity:
+ if (aContextPaint) {
+ opacity = aContextPaint->GetFillOpacity();
+ }
+ break;
+ case StyleSVGOpacity::Tag::ContextStrokeOpacity:
+ if (aContextPaint) {
+ opacity = aContextPaint->GetStrokeOpacity();
+ }
+ break;
+ }
+ return opacity;
+}
+
+bool SVGUtils::HasStroke(nsIFrame* aFrame, SVGContextPaint* aContextPaint) {
+ const nsStyleSVG* style = aFrame->StyleSVG();
+ return style->HasStroke() && GetStrokeWidth(aFrame, aContextPaint) > 0;
+}
+
+float SVGUtils::GetStrokeWidth(nsIFrame* aFrame,
+ SVGContextPaint* aContextPaint) {
+ nsIContent* content = aFrame->GetContent();
+ if (content->IsText()) {
+ content = content->GetParent();
+ }
+
+ auto* ctx = SVGElement::FromNode(content);
+ return SVGContentUtils::GetStrokeWidth(ctx, aFrame->Style(), aContextPaint);
+}
+
+void SVGUtils::SetupStrokeGeometry(nsIFrame* aFrame, gfxContext* aContext,
+ SVGContextPaint* aContextPaint) {
+ MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
+ SVGContentUtils::AutoStrokeOptions strokeOptions;
+ SVGContentUtils::GetStrokeOptions(&strokeOptions,
+ SVGElement::FromNode(aFrame->GetContent()),
+ aFrame->Style(), aContextPaint);
+
+ if (strokeOptions.mLineWidth <= 0) {
+ return;
+ }
+
+ // SVGContentUtils::GetStrokeOptions gets the stroke options in CSS px;
+ // convert to device pixels for gfxContext.
+ float devPxPerCSSPx = aFrame->PresContext()->CSSToDevPixelScale().scale;
+
+ aContext->SetLineWidth(strokeOptions.mLineWidth * devPxPerCSSPx);
+ aContext->SetLineCap(strokeOptions.mLineCap);
+ aContext->SetMiterLimit(strokeOptions.mMiterLimit);
+ aContext->SetLineJoin(strokeOptions.mLineJoin);
+ aContext->SetDash(strokeOptions.mDashPattern, strokeOptions.mDashLength,
+ strokeOptions.mDashOffset, devPxPerCSSPx);
+}
+
+uint16_t SVGUtils::GetGeometryHitTestFlags(nsIFrame* aFrame) {
+ uint16_t flags = 0;
+
+ switch (aFrame->Style()->PointerEvents()) {
+ case StylePointerEvents::None:
+ break;
+ case StylePointerEvents::Auto:
+ case StylePointerEvents::Visiblepainted:
+ if (aFrame->StyleVisibility()->IsVisible()) {
+ if (!aFrame->StyleSVG()->mFill.kind.IsNone())
+ flags |= SVG_HIT_TEST_FILL;
+ if (!aFrame->StyleSVG()->mStroke.kind.IsNone())
+ flags |= SVG_HIT_TEST_STROKE;
+ if (!aFrame->StyleSVG()->mStrokeOpacity.IsOpacity() ||
+ aFrame->StyleSVG()->mStrokeOpacity.AsOpacity() > 0)
+ flags |= SVG_HIT_TEST_CHECK_MRECT;
+ }
+ break;
+ case StylePointerEvents::Visiblefill:
+ if (aFrame->StyleVisibility()->IsVisible()) {
+ flags |= SVG_HIT_TEST_FILL;
+ }
+ break;
+ case StylePointerEvents::Visiblestroke:
+ if (aFrame->StyleVisibility()->IsVisible()) {
+ flags |= SVG_HIT_TEST_STROKE;
+ }
+ break;
+ case StylePointerEvents::Visible:
+ if (aFrame->StyleVisibility()->IsVisible()) {
+ flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE;
+ }
+ break;
+ case StylePointerEvents::Painted:
+ if (!aFrame->StyleSVG()->mFill.kind.IsNone()) flags |= SVG_HIT_TEST_FILL;
+ if (!aFrame->StyleSVG()->mStroke.kind.IsNone())
+ flags |= SVG_HIT_TEST_STROKE;
+ if (!aFrame->StyleSVG()->mStrokeOpacity.IsOpacity() ||
+ aFrame->StyleSVG()->mStrokeOpacity.AsOpacity() > 0) {
+ flags |= SVG_HIT_TEST_CHECK_MRECT;
+ }
+ break;
+ case StylePointerEvents::Fill:
+ flags |= SVG_HIT_TEST_FILL;
+ break;
+ case StylePointerEvents::Stroke:
+ flags |= SVG_HIT_TEST_STROKE;
+ break;
+ case StylePointerEvents::All:
+ flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE;
+ break;
+ default:
+ NS_ERROR("not reached");
+ break;
+ }
+
+ return flags;
+}
+
+void SVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext) {
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+ ISVGDisplayableFrame* svgFrame = do_QueryFrame(frame);
+ if (!svgFrame) {
+ return;
+ }
+ gfxMatrix m;
+ if (frame->GetContent()->IsSVGElement()) {
+ // PaintSVG() expects the passed transform to be the transform to its own
+ // SVG user space, so we need to account for any 'transform' attribute:
+ m = SVGUtils::GetTransformMatrixInUserSpace(frame);
+ }
+
+ // SVG-in-OpenType is not allowed to paint external resources, so we can
+ // just pass a dummy params into PatintSVG.
+ imgDrawingParams dummy;
+ svgFrame->PaintSVG(*aContext, m, dummy);
+}
+
+bool SVGUtils::GetSVGGlyphExtents(Element* aElement,
+ const gfxMatrix& aSVGToAppSpace,
+ gfxRect* aResult) {
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+ ISVGDisplayableFrame* svgFrame = do_QueryFrame(frame);
+ if (!svgFrame) {
+ return false;
+ }
+
+ gfxMatrix transform(aSVGToAppSpace);
+ if (auto* svg = SVGElement::FromNode(frame->GetContent())) {
+ transform = svg->PrependLocalTransformsTo(aSVGToAppSpace);
+ }
+
+ *aResult =
+ svgFrame
+ ->GetBBoxContribution(gfx::ToMatrix(transform),
+ SVGUtils::eBBoxIncludeFill |
+ SVGUtils::eBBoxIncludeFillGeometry |
+ SVGUtils::eBBoxIncludeStroke |
+ SVGUtils::eBBoxIncludeStrokeGeometry |
+ SVGUtils::eBBoxIncludeMarkers)
+ .ToThebesRect();
+ return true;
+}
+
+nsRect SVGUtils::ToCanvasBounds(const gfxRect& aUserspaceRect,
+ const gfxMatrix& aToCanvas,
+ const nsPresContext* presContext) {
+ return nsLayoutUtils::RoundGfxRectToAppRect(
+ aToCanvas.TransformBounds(aUserspaceRect),
+ presContext->AppUnitsPerDevPixel());
+}
+
+gfxMatrix SVGUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame) {
+ float devPxPerCSSPx = aNonSVGFrame->PresContext()->CSSToDevPixelScale().scale;
+
+ return gfxMatrix(devPxPerCSSPx, 0.0, 0.0, devPxPerCSSPx, 0.0, 0.0);
+}
+
+gfxMatrix SVGUtils::GetTransformMatrixInUserSpace(const nsIFrame* aFrame) {
+ // We check element instead of aFrame directly because SVG element
+ // may have non-SVG frame, <tspan> for example.
+ MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsSVGElement(),
+ "Only use this wrapper for SVG elements");
+
+ if (!aFrame->IsTransformed()) {
+ return {};
+ }
+
+ nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame);
+ nsDisplayTransform::FrameTransformProperties properties{
+ aFrame, refBox, AppUnitsPerCSSPixel()};
+
+ // SVG elements can have x/y offset, their default transform origin
+ // is the origin of user space, not the top left point of the frame.
+ Point3D svgTransformOrigin{
+ properties.mToTransformOrigin.x - CSSPixel::FromAppUnits(refBox.X()),
+ properties.mToTransformOrigin.y - CSSPixel::FromAppUnits(refBox.Y()),
+ properties.mToTransformOrigin.z};
+
+ Matrix svgTransform;
+ Matrix4x4 trans;
+ (void)aFrame->IsSVGTransformed(&svgTransform);
+
+ if (properties.HasTransform()) {
+ trans = nsStyleTransformMatrix::ReadTransforms(
+ properties.mTranslate, properties.mRotate, properties.mScale,
+ properties.mMotion, properties.mTransform, refBox,
+ AppUnitsPerCSSPixel());
+ } else {
+ trans = Matrix4x4::From2D(svgTransform);
+ }
+
+ trans.ChangeBasis(svgTransformOrigin);
+
+ Matrix mm;
+ trans.ProjectTo2D();
+ (void)trans.CanDraw2D(&mm);
+
+ return ThebesMatrix(mm);
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGUtils.h b/layout/svg/SVGUtils.h
new file mode 100644
index 0000000000..c9e24bea4e
--- /dev/null
+++ b/layout/svg/SVGUtils.h
@@ -0,0 +1,609 @@
+/* -*- 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 LAYOUT_SVG_SVGUTILS_H_
+#define LAYOUT_SVG_SVGUTILS_H_
+
+// include math.h to pick up definition of M_ maths defines e.g. M_PI
+#include <math.h>
+
+#include "DrawMode.h"
+#include "ImgDrawResult.h"
+#include "gfx2DGlue.h"
+#include "gfxMatrix.h"
+#include "gfxPoint.h"
+#include "gfxRect.h"
+#include "mozilla/gfx/Rect.h"
+#include "nsAlgorithm.h"
+#include "nsChangeHint.h"
+#include "nsColor.h"
+#include "nsCOMPtr.h"
+#include "nsID.h"
+#include "nsIFrame.h"
+#include "nsISupports.h"
+#include "nsMathUtils.h"
+#include "nsStyleStruct.h"
+#include <algorithm>
+
+class gfxContext;
+class nsFrameList;
+class nsIContent;
+
+class nsPresContext;
+class nsTextFrame;
+
+struct nsStyleSVG;
+struct nsRect;
+
+namespace mozilla {
+class SVGAnimatedEnumeration;
+class SVGAnimatedLength;
+class SVGContextPaint;
+struct SVGContextPaintImpl;
+class SVGDisplayContainerFrame;
+class SVGGeometryFrame;
+class SVGOuterSVGFrame;
+namespace dom {
+class Element;
+class SVGElement;
+class UserSpaceMetrics;
+} // namespace dom
+namespace gfx {
+class DrawTarget;
+class GeneralPattern;
+} // namespace gfx
+} // namespace mozilla
+
+// maximum dimension of an offscreen surface - choose so that
+// the surface size doesn't overflow a 32-bit signed int using
+// 4 bytes per pixel; in line with Factory::CheckSurfaceSize
+// In fact Macs can't even manage that
+#define NS_SVG_OFFSCREEN_MAX_DIMENSION 4096
+
+#define SVG_HIT_TEST_FILL 0x01
+#define SVG_HIT_TEST_STROKE 0x02
+#define SVG_HIT_TEST_CHECK_MRECT 0x04
+
+bool NS_SVGDisplayListHitTestingEnabled();
+bool NS_SVGDisplayListPaintingEnabled();
+bool NS_SVGNewGetBBoxEnabled();
+
+namespace mozilla {
+
+/**
+ * Sometimes we need to distinguish between an empty box and a box
+ * that contains an element that has no size e.g. a point at the origin.
+ */
+class SVGBBox final {
+ using Rect = gfx::Rect;
+
+ public:
+ SVGBBox() : mIsEmpty(true) {}
+
+ MOZ_IMPLICIT SVGBBox(const Rect& aRect) : mBBox(aRect), mIsEmpty(false) {}
+
+ MOZ_IMPLICIT SVGBBox(const gfxRect& aRect)
+ : mBBox(ToRect(aRect)), mIsEmpty(false) {}
+
+ operator const Rect&() { return mBBox; }
+
+ gfxRect ToThebesRect() const { return ThebesRect(mBBox); }
+
+ bool IsEmpty() const { return mIsEmpty; }
+
+ bool IsFinite() const { return mBBox.IsFinite(); }
+
+ void Scale(float aScale) { mBBox.Scale(aScale); }
+ void MoveBy(float x, float y) { mBBox.MoveBy(x, y); }
+
+ void UnionEdges(const SVGBBox& aSVGBBox) {
+ if (aSVGBBox.mIsEmpty) {
+ return;
+ }
+ mBBox = mIsEmpty ? aSVGBBox.mBBox : mBBox.UnionEdges(aSVGBBox.mBBox);
+ mIsEmpty = false;
+ }
+
+ void Intersect(const SVGBBox& aSVGBBox) {
+ if (!mIsEmpty && !aSVGBBox.mIsEmpty) {
+ mBBox = mBBox.Intersect(aSVGBBox.mBBox);
+ if (mBBox.IsEmpty()) {
+ mIsEmpty = true;
+ mBBox = Rect(0, 0, 0, 0);
+ }
+ } else {
+ mIsEmpty = true;
+ mBBox = Rect(0, 0, 0, 0);
+ }
+ }
+
+ private:
+ Rect mBBox;
+ bool mIsEmpty;
+};
+
+// GRRR WINDOWS HATE HATE HATE
+#undef CLIP_MASK
+
+class MOZ_RAII SVGAutoRenderState final {
+ using DrawTarget = gfx::DrawTarget;
+
+ public:
+ explicit SVGAutoRenderState(DrawTarget* aDrawTarget);
+ ~SVGAutoRenderState();
+
+ void SetPaintingToWindow(bool aPaintingToWindow);
+
+ static bool IsPaintingToWindow(DrawTarget* aDrawTarget);
+
+ private:
+ DrawTarget* mDrawTarget;
+ void* mOriginalRenderState;
+ bool mPaintingToWindow;
+};
+
+/**
+ * General functions used by all of SVG layout and possibly content code.
+ * If a method is used by content and depends only on other content methods
+ * it should go in SVGContentUtils instead.
+ */
+class SVGUtils final {
+ public:
+ using Element = dom::Element;
+ using SVGElement = dom::SVGElement;
+ using AntialiasMode = gfx::AntialiasMode;
+ using DrawTarget = gfx::DrawTarget;
+ using FillRule = gfx::FillRule;
+ using GeneralPattern = gfx::GeneralPattern;
+ using Size = gfx::Size;
+ using imgDrawingParams = image::imgDrawingParams;
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(ObjectBoundingBoxProperty, gfxRect)
+
+ /**
+ * Returns the frame's post-filter ink overflow rect when passed the
+ * frame's pre-filter ink overflow rect. If the frame is not currently
+ * being filtered, this function simply returns aUnfilteredRect.
+ */
+ static nsRect GetPostFilterInkOverflowRect(nsIFrame* aFrame,
+ const nsRect& aPreFilterRect);
+
+ /**
+ * Schedules an update of the frame's bounds (which will in turn invalidate
+ * the new area that the frame should paint to).
+ *
+ * This does nothing when passed an NS_FRAME_IS_NONDISPLAY frame.
+ * In future we may want to allow ReflowSVG to be called on such frames,
+ * but that would be better implemented as a ForceReflowSVG function to
+ * be called synchronously while painting them without marking or paying
+ * attention to dirty bits like this function.
+ *
+ * This is very similar to PresShell::FrameNeedsReflow. The main reason that
+ * we have this function instead of using FrameNeedsReflow is because we need
+ * to be able to call it under SVGOuterSVGFrame::NotifyViewportChange when
+ * that function is called by SVGOuterSVGFrame::Reflow. FrameNeedsReflow
+ * is not suitable for calling during reflow though, and it asserts as much.
+ * The reason that we want to be callable under NotifyViewportChange is
+ * because we want to synchronously notify and dirty the SVGOuterSVGFrame's
+ * children so that when SVGOuterSVGFrame::DidReflow is called its children
+ * will be updated for the new size as appropriate. Otherwise we'd have to
+ * post an event to the event loop to mark dirty flags and request an update.
+ *
+ * Another reason that we don't currently want to call
+ * PresShell::FrameNeedsReflow is because passing eRestyle to it to get it to
+ * mark descendants dirty would cause it to descend through
+ * SVGForeignObjectFrame frames to mark their children dirty, but we want to
+ * handle SVGForeignObjectFrame specially. It would also do unnecessary work
+ * descending into NS_FRAME_IS_NONDISPLAY frames.
+ */
+ static void ScheduleReflowSVG(nsIFrame* aFrame);
+
+ /**
+ * Returns true if the frame or any of its children need ReflowSVG
+ * to be called on them.
+ */
+ static bool NeedsReflowSVG(nsIFrame* aFrame);
+
+ /**
+ * Percentage lengths in SVG are resolved against the width/height of the
+ * nearest viewport (or its viewBox, if set). This helper returns the size
+ * of this "context" for the given frame so that percentage values can be
+ * resolved.
+ */
+ static Size GetContextSize(const nsIFrame* aFrame);
+
+ /* Computes the input length in terms of object space coordinates.
+ Input: rect - bounding box
+ length - length to be converted
+ */
+ static float ObjectSpace(const gfxRect& aRect,
+ const SVGAnimatedLength* aLength);
+
+ /* Computes the input length in terms of user space coordinates.
+ Input: content - object to be used for determining user space
+ Input: length - length to be converted
+ */
+ static float UserSpace(SVGElement* aSVGElement,
+ const SVGAnimatedLength* aLength);
+ static float UserSpace(nsIFrame* aNonSVGContext,
+ const SVGAnimatedLength* aLength);
+ static float UserSpace(const dom::UserSpaceMetrics& aMetrics,
+ const SVGAnimatedLength* aLength);
+
+ /* Find the outermost SVG frame of the passed frame */
+ static SVGOuterSVGFrame* GetOuterSVGFrame(nsIFrame* aFrame);
+
+ /**
+ * Get the covered region for a frame. Return null if it's not an SVG frame.
+ * @param aRect gets a rectangle in app units
+ * @return the outer SVG frame which aRect is relative to
+ */
+ static nsIFrame* GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame,
+ nsRect* aRect);
+
+ /* Paint SVG frame with SVG effects - aDirtyRect is the area being
+ * redrawn, in device pixel coordinates relative to the outer svg */
+ static void PaintFrameWithEffects(nsIFrame* aFrame, gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect = nullptr);
+
+ /* Hit testing - check if point hits the clipPath of indicated
+ * frame. Returns true if no clipPath set. */
+ static bool HitTestClip(nsIFrame* aFrame, const gfxPoint& aPoint);
+
+ /**
+ * Hit testing - check if point hits any children of aFrame. aPoint is
+ * expected to be in the coordinate space established by aFrame for its
+ * children (e.g. the space established by the 'viewBox' attribute on <svg>).
+ */
+ static nsIFrame* HitTestChildren(SVGDisplayContainerFrame* aFrame,
+ const gfxPoint& aPoint);
+
+ /*
+ * Returns the CanvasTM of the indicated frame, whether it's a
+ * child SVG frame, container SVG frame, or a regular frame.
+ * For regular frames, we just return an identity matrix.
+ */
+ static gfxMatrix GetCanvasTM(nsIFrame* aFrame);
+
+ /**
+ * Notify the descendants of aFrame of a change to one of their ancestors
+ * that might affect them.
+ */
+ static void NotifyChildrenOfSVGChange(nsIFrame* aFrame, uint32_t aFlags);
+
+ static nsRect TransformFrameRectToOuterSVG(const nsRect& aRect,
+ const gfxMatrix& aMatrix,
+ nsPresContext* aPresContext);
+
+ /*
+ * Convert a surface size to an integer for use by thebes
+ * possibly making it smaller in the process so the surface does not
+ * use excessive memory.
+ *
+ * @param aSize the desired surface size
+ * @param aResultOverflows true if the desired surface size is too big
+ * @return the surface size to use
+ */
+ static gfx::IntSize ConvertToSurfaceSize(const gfxSize& aSize,
+ bool* aResultOverflows);
+
+ /*
+ * Hit test a given rectangle/matrix.
+ */
+ static bool HitTestRect(const gfx::Matrix& aMatrix, float aRX, float aRY,
+ float aRWidth, float aRHeight, float aX, float aY);
+
+ /**
+ * Get the clip rect for the given frame, taking into account the CSS 'clip'
+ * property. See:
+ * http://www.w3.org/TR/SVG11/masking.html#OverflowAndClipProperties
+ * The arguments for aX, aY, aWidth and aHeight should be the dimensions of
+ * the viewport established by aFrame.
+ */
+ static gfxRect GetClipRectForFrame(nsIFrame* aFrame, float aX, float aY,
+ float aWidth, float aHeight);
+
+ static void SetClipRect(gfxContext* aContext, const gfxMatrix& aCTM,
+ const gfxRect& aRect);
+
+ /* Using group opacity instead of fill or stroke opacity on a
+ * geometry object seems to be a common authoring mistake. If we're
+ * not applying filters and not both stroking and filling, we can
+ * generate the same result without going through the overhead of a
+ * push/pop group. */
+ static bool CanOptimizeOpacity(nsIFrame* aFrame);
+
+ /**
+ * Take the CTM to userspace for an element, and adjust it to a CTM to its
+ * object bounding box space if aUnits is SVG_UNIT_TYPE_OBJECTBOUNDINGBOX.
+ * (I.e. so that [0,0] is at the top left of its bbox, and [1,1] is at the
+ * bottom right of its bbox).
+ *
+ * If the bbox is empty, this will return a singular matrix.
+ *
+ * @param aFlags One or more of the BBoxFlags values defined below.
+ */
+ static gfxMatrix AdjustMatrixForUnits(const gfxMatrix& aMatrix,
+ SVGAnimatedEnumeration* aUnits,
+ nsIFrame* aFrame, uint32_t aFlags);
+
+ enum BBoxFlags {
+ eBBoxIncludeFill = 1 << 0,
+ // Include the geometry of the fill even when the fill does not
+ // actually render (e.g. when fill="none" or fill-opacity="0")
+ eBBoxIncludeFillGeometry = 1 << 1,
+ eBBoxIncludeStroke = 1 << 2,
+ // Include the geometry of the stroke even when the stroke does not
+ // actually render (e.g. when stroke="none" or stroke-opacity="0")
+ eBBoxIncludeStrokeGeometry = 1 << 3,
+ eBBoxIncludeMarkers = 1 << 4,
+ eBBoxIncludeClipped = 1 << 5,
+ // Normally a getBBox call on outer-<svg> should only return the
+ // bounds of the elements children. This flag will cause the
+ // element's bounds to be returned instead.
+ eUseFrameBoundsForOuterSVG = 1 << 6,
+ // https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
+ eForGetClientRects = 1 << 7,
+ // If the given frame is an HTML element, only include the region of the
+ // given frame, instead of all continuations of it, while computing bbox if
+ // this flag is set.
+ eIncludeOnlyCurrentFrameForNonSVGElement = 1 << 8,
+ // This flag is only has an effect when the target is a <use> element.
+ // getBBox returns the bounds of the elements children in user space if
+ // this flag is set; Otherwise, getBBox returns the union bounds in
+ // the coordinate system formed by the <use> element.
+ eUseUserSpaceOfUseElement = 1 << 9,
+ // For a frame with a clip-path, if this flag is set then the result
+ // will not be clipped to the bbox of the content inside the clip-path.
+ eDoNotClipToBBoxOfContentInsideClipPath = 1 << 10,
+ };
+ /**
+ * This function in primarily for implementing the SVG DOM function getBBox()
+ * and the SVG attribute value 'objectBoundingBox'. However, it has been
+ * extended with various extra parameters in order to become more of a
+ * general purpose getter of all sorts of bounds that we might need to obtain
+ * for SVG elements, or even for other elements that have SVG effects applied
+ * to them.
+ *
+ * @param aFrame The frame of the element for which the bounds are to be
+ * obtained.
+ * @param aFlags One or more of the BBoxFlags values defined above.
+ * @param aToBoundsSpace If not specified the returned rect is in aFrame's
+ * element's "user space". A matrix can optionally be pass to specify a
+ * transform from aFrame's user space to the bounds space of interest
+ * (typically this will be the ancestor SVGOuterSVGFrame, but it could be
+ * to any other coordinate space).
+ */
+ static gfxRect GetBBox(nsIFrame* aFrame,
+ // If the default arg changes, update the handling for
+ // ObjectBoundingBoxProperty() in the implementation.
+ uint32_t aFlags = eBBoxIncludeFillGeometry,
+ const gfxMatrix* aToBoundsSpace = nullptr);
+
+ /*
+ * "User space" is the space that the frame's BBox (as calculated by
+ * SVGUtils::GetBBox) is in. "Frame space" is the space that has its origin
+ * at the top left of the union of the frame's border-box rects over all
+ * continuations.
+ * This function returns the offset one needs to add to something in frame
+ * space in order to get its coordinates in user space.
+ */
+ static gfxPoint FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame* aFrame);
+
+ /**
+ * Convert a userSpaceOnUse/objectBoundingBoxUnits rectangle that's specified
+ * using four SVGAnimatedLength values into a user unit rectangle in user
+ * space.
+ *
+ * @param aXYWH pointer to 4 consecutive SVGAnimatedLength objects containing
+ * the x, y, width and height values in that order
+ * @param aBBox the bounding box of the object the rect is relative to;
+ * may be null if aUnits is not SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
+ * @param aFrame the object in which to interpret user-space units;
+ * may be null if aUnits is SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
+ */
+ static gfxRect GetRelativeRect(uint16_t aUnits,
+ const SVGAnimatedLength* aXYWH,
+ const gfxRect& aBBox, nsIFrame* aFrame);
+
+ static gfxRect GetRelativeRect(uint16_t aUnits,
+ const SVGAnimatedLength* aXYWH,
+ const gfxRect& aBBox,
+ const dom::UserSpaceMetrics& aMetrics);
+
+ /**
+ * Find the first frame, starting with aStartFrame and going up its
+ * parent chain, that is not an svgAFrame.
+ */
+ static nsIFrame* GetFirstNonAAncestorFrame(nsIFrame* aStartFrame);
+
+ static bool OuterSVGIsCallingReflowSVG(nsIFrame* aFrame);
+ static bool AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame);
+
+ /**
+ * See https://svgwg.org/svg2-draft/painting.html#NonScalingStroke
+ *
+ * If the computed value of the 'vector-effect' property on aFrame is
+ * 'non-scaling-stroke', then this function will set aUserToOuterSVG to the
+ * transform from aFrame's SVG user space to the initial coordinate system
+ * established by the viewport of aFrame's outer-<svg>'s (the coordinate
+ * system in which the stroke is fixed). If aUserToOuterSVG is set to a
+ * non-identity matrix this function returns true, else it returns false.
+ */
+ static bool GetNonScalingStrokeTransform(nsIFrame* aFrame,
+ gfxMatrix* aUserToOuterSVG);
+
+ /**
+ * Compute the maximum possible device space stroke extents of a path given
+ * the path's device space path extents, its stroke style and its ctm.
+ *
+ * This is a workaround for the lack of suitable cairo API for getting the
+ * tight device space stroke extents of a path. This basically gives us the
+ * tightest extents that we can guarantee fully enclose the inked stroke
+ * without doing the calculations for the actual tight extents. We exploit
+ * the fact that cairo does have an API for getting the tight device space
+ * fill/path extents.
+ *
+ * This should die once bug 478152 is fixed.
+ */
+ static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
+ nsTextFrame* aFrame,
+ const gfxMatrix& aMatrix);
+ static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
+ SVGGeometryFrame* aFrame,
+ const gfxMatrix& aMatrix);
+
+ /**
+ * Convert a floating-point value to a 32-bit integer value, clamping to
+ * the range of valid integers.
+ */
+ static int32_t ClampToInt(double aVal) {
+ return NS_lround(
+ std::max(double(INT32_MIN), std::min(double(INT32_MAX), aVal)));
+ }
+
+ /**
+ * Convert a floating-point value to a 64-bit integer value, clamping to
+ * the lowest and highest integers that can be safely compared to a double.
+ */
+ static int64_t ClampToInt64(double aVal) {
+ return static_cast<int64_t>(
+ std::clamp<double>(aVal, INT64_MIN, std::nexttoward(INT64_MAX, 0)));
+ }
+
+ static nscolor GetFallbackOrPaintColor(
+ const ComputedStyle&, StyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ nscolor aDefaultContextFallbackColor);
+
+ static void MakeFillPatternFor(nsIFrame* aFrame, gfxContext* aContext,
+ GeneralPattern* aOutPattern,
+ imgDrawingParams& aImgParams,
+ SVGContextPaint* aContextPaint = nullptr);
+
+ static void MakeStrokePatternFor(nsIFrame* aFrame, gfxContext* aContext,
+ GeneralPattern* aOutPattern,
+ imgDrawingParams& aImgParams,
+ SVGContextPaint* aContextPaint = nullptr);
+
+ static float GetOpacity(const StyleSVGOpacity&, SVGContextPaint*);
+
+ /*
+ * @return false if there is no stroke
+ */
+ static bool HasStroke(nsIFrame* aFrame,
+ SVGContextPaint* aContextPaint = nullptr);
+
+ static float GetStrokeWidth(nsIFrame* aFrame,
+ SVGContextPaint* aContextPaint = nullptr);
+
+ /*
+ * Set up a context for a stroked path (including any dashing that applies).
+ */
+ static void SetupStrokeGeometry(nsIFrame* aFrame, gfxContext* aContext,
+ SVGContextPaint* aContextPaint = nullptr);
+
+ /**
+ * This function returns a set of bit flags indicating which parts of the
+ * element (fill, stroke, bounds) should intercept pointer events. It takes
+ * into account the type of element and the value of the 'pointer-events'
+ * property on the element.
+ */
+ static uint16_t GetGeometryHitTestFlags(nsIFrame* aFrame);
+
+ static FillRule ToFillRule(StyleFillRule aFillRule) {
+ return aFillRule == StyleFillRule::Evenodd ? FillRule::FILL_EVEN_ODD
+ : FillRule::FILL_WINDING;
+ }
+
+ static AntialiasMode ToAntialiasMode(StyleTextRendering aTextRendering) {
+ return aTextRendering == StyleTextRendering::Optimizespeed
+ ? AntialiasMode::NONE
+ : AntialiasMode::SUBPIXEL;
+ }
+
+ /**
+ * Render a SVG glyph.
+ * @param aElement the SVG glyph element to render
+ * @param aContext the thebes aContext to draw to
+ * @return true if rendering succeeded
+ */
+ static void PaintSVGGlyph(Element* aElement, gfxContext* aContext);
+
+ /**
+ * Get the extents of a SVG glyph.
+ * @param aElement the SVG glyph element
+ * @param aSVGToAppSpace the matrix mapping the SVG glyph space to the
+ * target context space
+ * @param aResult the result (valid when true is returned)
+ * @return true if calculating the extents succeeded
+ */
+ static bool GetSVGGlyphExtents(Element* aElement,
+ const gfxMatrix& aSVGToAppSpace,
+ gfxRect* aResult);
+
+ /**
+ * Returns the app unit canvas bounds of a userspace rect.
+ *
+ * @param aToCanvas Transform from userspace to canvas device space.
+ */
+ static nsRect ToCanvasBounds(const gfxRect& aUserspaceRect,
+ const gfxMatrix& aToCanvas,
+ const nsPresContext* presContext);
+
+ struct MaskUsage {
+ bool shouldGenerateMaskLayer;
+ bool shouldGenerateClipMaskLayer;
+ bool shouldApplyClipPath;
+ bool shouldApplyBasicShapeOrPath;
+ float opacity;
+
+ MaskUsage()
+ : shouldGenerateMaskLayer(false),
+ shouldGenerateClipMaskLayer(false),
+ shouldApplyClipPath(false),
+ shouldApplyBasicShapeOrPath(false),
+ opacity(0.0) {}
+
+ bool shouldDoSomething() {
+ return shouldGenerateMaskLayer || shouldGenerateClipMaskLayer ||
+ shouldApplyClipPath || shouldApplyBasicShapeOrPath ||
+ opacity != 1.0;
+ }
+ };
+
+ static void DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity,
+ MaskUsage& aUsage);
+
+ static float ComputeOpacity(nsIFrame* aFrame, bool aHandleOpacity);
+
+ /**
+ * SVG frames expect to paint in SVG user units, which are equal to CSS px
+ * units. This method provides a transform matrix to multiply onto a
+ * gfxContext's current transform to convert the context's current units from
+ * its usual dev pixels to SVG user units/CSS px to keep the SVG code happy.
+ */
+ static gfxMatrix GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame);
+
+ static bool IsInSVGTextSubtree(const nsIFrame* aFrame) {
+ // Returns true if the frame is an SVGTextFrame or one of its descendants.
+ return aFrame->HasAnyStateBits(NS_FRAME_IS_SVG_TEXT);
+ }
+
+ /**
+ * It is a replacement of
+ * SVGElement::PrependLocalTransformsTo(eUserSpaceToParent).
+ * If no CSS transform is involved, they should behave exactly the same;
+ * if there are CSS transforms, this one will take them into account
+ * while SVGElement::PrependLocalTransformsTo won't.
+ */
+ static gfxMatrix GetTransformMatrixInUserSpace(const nsIFrame* aFrame);
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGUTILS_H_
diff --git a/layout/svg/SVGViewFrame.cpp b/layout/svg/SVGViewFrame.cpp
new file mode 100644
index 0000000000..e7105ceb6c
--- /dev/null
+++ b/layout/svg/SVGViewFrame.cpp
@@ -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/. */
+
+// Keep in (case-insensitive) order:
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGOuterSVGFrame.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/SVGSVGElement.h"
+#include "mozilla/dom/SVGViewElement.h"
+#include "nsIFrame.h"
+#include "nsGkAtoms.h"
+
+using namespace mozilla::dom;
+
+nsIFrame* NS_NewSVGViewFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle);
+
+namespace mozilla {
+
+/**
+ * While views are not directly rendered in SVG they can be linked to
+ * and thereby override attributes of an <svg> element via a fragment
+ * identifier. The SVGViewFrame class passes on any attribute changes
+ * the view receives to the overridden <svg> element (if there is one).
+ **/
+class SVGViewFrame final : public nsIFrame {
+ friend nsIFrame* ::NS_NewSVGViewFrame(mozilla::PresShell* aPresShell,
+ ComputedStyle* aStyle);
+
+ protected:
+ explicit SVGViewFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
+ : nsIFrame(aStyle, aPresContext, kClassID) {
+ AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY);
+ }
+
+ public:
+ NS_DECL_FRAMEARENA_HELPERS(SVGViewFrame)
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ bool IsFrameOfType(uint32_t aFlags) const override {
+ if (aFlags & eSupportsContainLayoutAndPaint) {
+ return false;
+ }
+
+ return nsIFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(u"SVGView"_ns, aResult);
+ }
+#endif
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ bool ComputeCustomOverflow(OverflowAreas& aOverflowAreas) override {
+ // We don't maintain a ink overflow rect
+ return false;
+ }
+};
+
+} // namespace mozilla
+
+nsIFrame* NS_NewSVGViewFrame(mozilla::PresShell* aPresShell,
+ mozilla::ComputedStyle* aStyle) {
+ return new (aPresShell)
+ mozilla::SVGViewFrame(aStyle, aPresShell->GetPresContext());
+}
+
+namespace mozilla {
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGViewFrame)
+
+#ifdef DEBUG
+void SVGViewFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::view),
+ "Content is not an SVG view");
+
+ nsIFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsresult SVGViewFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType) {
+ // Ignore zoomAndPan as it does not cause the <svg> element to re-render
+
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::preserveAspectRatio ||
+ aAttribute == nsGkAtoms::viewBox)) {
+ SVGOuterSVGFrame* outerSVGFrame = SVGUtils::GetOuterSVGFrame(this);
+ NS_ASSERTION(outerSVGFrame->GetContent()->IsSVGElement(nsGkAtoms::svg),
+ "Expecting an <svg> element");
+
+ SVGSVGElement* svgElement =
+ static_cast<SVGSVGElement*>(outerSVGFrame->GetContent());
+
+ nsAutoString viewID;
+ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::id, viewID);
+
+ if (svgElement->IsOverriddenBy(viewID)) {
+ // We're the view that's providing overrides, so pretend that the frame
+ // we're overriding was updated.
+ outerSVGFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType);
+ }
+ }
+
+ return nsIFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGViewportFrame.cpp b/layout/svg/SVGViewportFrame.cpp
new file mode 100644
index 0000000000..0b351dfa1b
--- /dev/null
+++ b/layout/svg/SVGViewportFrame.cpp
@@ -0,0 +1,273 @@
+/* -*- 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:
+#include "SVGViewportFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "mozilla/ISVGDisplayableFrame.h"
+#include "mozilla/SVGContainerFrame.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/SVGViewportElement.h"
+#include "nsLayoutUtils.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+namespace mozilla {
+
+//----------------------------------------------------------------------
+// ISVGDisplayableFrame methods
+
+void SVGViewportFrame::PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect) {
+ NS_ASSERTION(
+ !NS_SVGDisplayListPaintingEnabled() || (mState & NS_FRAME_IS_NONDISPLAY),
+ "If display lists are enabled, only painting of non-display "
+ "SVG should take this code path");
+
+ gfxContextAutoSaveRestore autoSR;
+
+ if (StyleDisplay()->IsScrollableOverflow()) {
+ float x, y, width, height;
+ static_cast<SVGViewportElement*>(GetContent())
+ ->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
+
+ if (width <= 0 || height <= 0) {
+ return;
+ }
+
+ autoSR.SetContext(&aContext);
+ gfxRect clipRect = SVGUtils::GetClipRectForFrame(this, x, y, width, height);
+ SVGUtils::SetClipRect(&aContext, aTransform, clipRect);
+ }
+
+ SVGDisplayContainerFrame::PaintSVG(aContext, aTransform, aImgParams,
+ aDirtyRect);
+}
+
+void SVGViewportFrame::ReflowSVG() {
+ // mRect must be set before FinishAndStoreOverflow is called in order
+ // for our overflow areas to be clipped correctly.
+ float x, y, width, height;
+ static_cast<SVGViewportElement*>(GetContent())
+ ->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
+ mRect = nsLayoutUtils::RoundGfxRectToAppRect(gfxRect(x, y, width, height),
+ AppUnitsPerCSSPixel());
+
+ // If we have a filter, we need to invalidate ourselves because filter
+ // output can change even if none of our descendants need repainting.
+ if (StyleEffects()->HasFilters()) {
+ InvalidateFrame();
+ }
+
+ SVGDisplayContainerFrame::ReflowSVG();
+}
+
+void SVGViewportFrame::NotifySVGChanged(uint32_t aFlags) {
+ MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
+ "Invalidation logic may need adjusting");
+
+ if (aFlags & COORD_CONTEXT_CHANGED) {
+ SVGViewportElement* svg = static_cast<SVGViewportElement*>(GetContent());
+
+ bool xOrYIsPercentage =
+ svg->mLengthAttributes[SVGViewportElement::ATTR_X].IsPercentage() ||
+ svg->mLengthAttributes[SVGViewportElement::ATTR_Y].IsPercentage();
+ bool widthOrHeightIsPercentage =
+ svg->mLengthAttributes[SVGViewportElement::ATTR_WIDTH].IsPercentage() ||
+ svg->mLengthAttributes[SVGViewportElement::ATTR_HEIGHT].IsPercentage();
+
+ if (xOrYIsPercentage || widthOrHeightIsPercentage) {
+ // Ancestor changes can't affect how we render from the perspective of
+ // any rendering observers that we may have, so we don't need to
+ // invalidate them. We also don't need to invalidate ourself, since our
+ // changed ancestor will have invalidated its entire area, which includes
+ // our area.
+ // For perf reasons we call this before calling NotifySVGChanged() below.
+ SVGUtils::ScheduleReflowSVG(this);
+ }
+
+ // Coordinate context changes affect mCanvasTM if we have a
+ // percentage 'x' or 'y', or if we have a percentage 'width' or 'height' AND
+ // a 'viewBox'.
+
+ if (!(aFlags & TRANSFORM_CHANGED) &&
+ (xOrYIsPercentage ||
+ (widthOrHeightIsPercentage && svg->HasViewBox()))) {
+ aFlags |= TRANSFORM_CHANGED;
+ }
+
+ if (svg->HasViewBox() || !widthOrHeightIsPercentage) {
+ // Remove COORD_CONTEXT_CHANGED, since we establish the coordinate
+ // context for our descendants and this notification won't change its
+ // dimensions:
+ aFlags &= ~COORD_CONTEXT_CHANGED;
+
+ if (!aFlags) {
+ return; // No notification flags left
+ }
+ }
+ }
+
+ SVGDisplayContainerFrame::NotifySVGChanged(aFlags);
+}
+
+SVGBBox SVGViewportFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace,
+ uint32_t aFlags) {
+ // XXXjwatt It seems like authors would want the result to be clipped by the
+ // viewport we establish if IsScrollableOverflow() is true. We should
+ // consider doing that. See bug 1350755.
+
+ SVGBBox bbox;
+
+ if (aFlags & SVGUtils::eForGetClientRects) {
+ // XXXjwatt For consistency with the old code this code includes the
+ // viewport we establish in the result, but only includes the bounds of our
+ // descendants if they are not clipped to that viewport. However, this is
+ // both inconsistent with Chrome and with the specs. See bug 1350755.
+ // Ideally getClientRects/getBoundingClientRect should be consistent with
+ // getBBox.
+ float x, y, w, h;
+ static_cast<SVGViewportElement*>(GetContent())
+ ->GetAnimatedLengthValues(&x, &y, &w, &h, nullptr);
+ if (w < 0.0f) w = 0.0f;
+ if (h < 0.0f) h = 0.0f;
+ Rect viewport(x, y, w, h);
+ bbox = aToBBoxUserspace.TransformBounds(viewport);
+ if (StyleDisplay()->IsScrollableOverflow()) {
+ return bbox;
+ }
+ // Else we're not clipping to our viewport so we fall through and include
+ // the bounds of our children.
+ }
+
+ SVGBBox descendantsBbox =
+ SVGDisplayContainerFrame::GetBBoxContribution(aToBBoxUserspace, aFlags);
+
+ bbox.UnionEdges(descendantsBbox);
+
+ return bbox;
+}
+
+nsresult SVGViewportFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ if (aNameSpaceID == kNameSpaceID_None &&
+ !HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ SVGViewportElement* content =
+ static_cast<SVGViewportElement*>(GetContent());
+
+ if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height) {
+ nsLayoutUtils::PostRestyleEvent(
+ mContent->AsElement(), RestyleHint{0},
+ nsChangeHint_InvalidateRenderingObservers);
+ SVGUtils::ScheduleReflowSVG(this);
+
+ if (content->HasViewBoxOrSyntheticViewBox()) {
+ // make sure our cached transform matrix gets (lazily) updated
+ mCanvasTM = nullptr;
+ content->ChildrenOnlyTransformChanged();
+ SVGUtils::NotifyChildrenOfSVGChange(this, TRANSFORM_CHANGED);
+ } else {
+ uint32_t flags = COORD_CONTEXT_CHANGED;
+ if (mCanvasTM && mCanvasTM->IsSingular()) {
+ mCanvasTM = nullptr;
+ flags |= TRANSFORM_CHANGED;
+ }
+ SVGUtils::NotifyChildrenOfSVGChange(this, flags);
+ }
+
+ } else if (aAttribute == nsGkAtoms::transform ||
+ aAttribute == nsGkAtoms::preserveAspectRatio ||
+ aAttribute == nsGkAtoms::viewBox || aAttribute == nsGkAtoms::x ||
+ aAttribute == nsGkAtoms::y) {
+ // make sure our cached transform matrix gets (lazily) updated
+ mCanvasTM = nullptr;
+
+ SVGUtils::NotifyChildrenOfSVGChange(
+ this, aAttribute == nsGkAtoms::viewBox
+ ? TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED
+ : TRANSFORM_CHANGED);
+
+ // We don't invalidate for transform changes (the layers code does that).
+ // Also note that SVGTransformableElement::GetAttributeChangeHint will
+ // return nsChangeHint_UpdateOverflow for "transform" attribute changes
+ // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
+
+ if (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y) {
+ nsLayoutUtils::PostRestyleEvent(
+ mContent->AsElement(), RestyleHint{0},
+ nsChangeHint_InvalidateRenderingObservers);
+ SVGUtils::ScheduleReflowSVG(this);
+ } else if (aAttribute == nsGkAtoms::viewBox ||
+ (aAttribute == nsGkAtoms::preserveAspectRatio &&
+ content->HasViewBoxOrSyntheticViewBox())) {
+ content->ChildrenOnlyTransformChanged();
+ // SchedulePaint sets a global state flag so we only need to call it
+ // once (on ourself is fine), not once on each child (despite bug
+ // 828240).
+ SchedulePaint();
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsIFrame* SVGViewportFrame::GetFrameForPoint(const gfxPoint& aPoint) {
+ NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "If display lists are enabled, only hit-testing of non-display "
+ "SVG should take this code path");
+
+ if (StyleDisplay()->IsScrollableOverflow()) {
+ Rect clip;
+ static_cast<SVGElement*>(GetContent())
+ ->GetAnimatedLengthValues(&clip.x, &clip.y, &clip.width, &clip.height,
+ nullptr);
+ if (!clip.Contains(ToPoint(aPoint))) {
+ return nullptr;
+ }
+ }
+
+ return SVGDisplayContainerFrame::GetFrameForPoint(aPoint);
+}
+
+//----------------------------------------------------------------------
+// ISVGSVGFrame methods:
+
+void SVGViewportFrame::NotifyViewportOrTransformChanged(uint32_t aFlags) {
+ // The dimensions of inner-<svg> frames are purely defined by their "width"
+ // and "height" attributes, and transform changes can only occur as a result
+ // of changes to their "width", "height", "viewBox" or "preserveAspectRatio"
+ // attributes. Changes to all of these attributes are handled in
+ // AttributeChanged(), so we should never be called.
+ NS_ERROR("Not called for SVGViewportFrame");
+}
+
+//----------------------------------------------------------------------
+// SVGContainerFrame methods:
+
+bool SVGViewportFrame::HasChildrenOnlyTransform(gfx::Matrix* aTransform) const {
+ SVGViewportElement* content = static_cast<SVGViewportElement*>(GetContent());
+
+ if (content->HasViewBoxOrSyntheticViewBox()) {
+ // XXX Maybe return false if the transform is the identity transform?
+ if (aTransform) {
+ *aTransform = content->GetViewBoxTransform();
+ }
+ return true;
+ }
+ return false;
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGViewportFrame.h b/layout/svg/SVGViewportFrame.h
new file mode 100644
index 0000000000..9c6b23289f
--- /dev/null
+++ b/layout/svg/SVGViewportFrame.h
@@ -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/. */
+
+#ifndef LAYOUT_SVG_SVGVIEWPORTFRAME_H_
+#define LAYOUT_SVG_SVGVIEWPORTFRAME_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ISVGSVGFrame.h"
+#include "mozilla/SVGContainerFrame.h"
+
+class gfxContext;
+
+namespace mozilla {
+
+/**
+ * Superclass for inner SVG frames and symbol frames.
+ */
+class SVGViewportFrame : public SVGDisplayContainerFrame, public ISVGSVGFrame {
+ protected:
+ SVGViewportFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ nsIFrame::ClassID aID)
+ : SVGDisplayContainerFrame(aStyle, aPresContext, aID) {}
+
+ public:
+ NS_DECL_ABSTRACT_FRAME(SVGViewportFrame)
+
+ nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) override;
+
+ // ISVGDisplayableFrame interface:
+ void PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
+ imgDrawingParams& aImgParams,
+ const nsIntRect* aDirtyRect = nullptr) override;
+ void ReflowSVG() override;
+ void NotifySVGChanged(uint32_t aFlags) override;
+ SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace,
+ uint32_t aFlags) override;
+ nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
+
+ // SVGContainerFrame methods:
+ bool HasChildrenOnlyTransform(Matrix* aTransform) const override;
+
+ // ISVGSVGFrame interface:
+ void NotifyViewportOrTransformChanged(uint32_t aFlags) override;
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_SVG_SVGVIEWPORTFRAME_H_
diff --git a/layout/svg/crashtests/1016145.svg b/layout/svg/crashtests/1016145.svg
new file mode 100644
index 0000000000..5c362a17e1
--- /dev/null
+++ b/layout/svg/crashtests/1016145.svg
@@ -0,0 +1,5 @@
+<!-- svg mime type but html root -->
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <body style="display: table-column-group;" />
+</html>
+
diff --git a/layout/svg/crashtests/1028512.svg b/layout/svg/crashtests/1028512.svg
new file mode 100644
index 0000000000..0c9458b478
--- /dev/null
+++ b/layout/svg/crashtests/1028512.svg
@@ -0,0 +1,15 @@
+<!-- {lower,upper}-{roman,alpha} in svg -->
+<svg xmlns="http://www.w3.org/2000/svg">
+<script>
+<![CDATA[
+
+function boom()
+{
+ document.documentElement.style.transition = "2s";
+ document.documentElement.style.listStyleType = "lower-roman";
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script></svg>
diff --git a/layout/svg/crashtests/1072758.html b/layout/svg/crashtests/1072758.html
new file mode 100644
index 0000000000..1d4c85fb87
--- /dev/null
+++ b/layout/svg/crashtests/1072758.html
@@ -0,0 +1,35 @@
+<style>
+#x9 {
+ display:none;
+}
+</style>
+
+<body onload="go()">
+<svg>
+<path id="a"></path>
+
+<mask id="m">
+ <text id="y">
+ <tspan id="x1"></tspan>
+ <textPath id="x2"></textPath>
+ <a id="x3">Hello</a>
+ <tspan><tspan id="x4"></tspan></tspan>
+ <tspan id="x5"></tspan>
+ </text>
+</mask>
+
+<rect width="600" height="400" mask="url(#m)"/>
+</svg>
+</body>
+
+<script>
+
+function go() {
+ x1.style.display = "none";
+ x2.style.display = "none";
+ x3.style.display = "none";
+ x4.style.display = "none";
+ x5.id = "x9";
+};
+
+</script>
diff --git a/layout/svg/crashtests/1140080-1.svg b/layout/svg/crashtests/1140080-1.svg
new file mode 100644
index 0000000000..d424562468
--- /dev/null
+++ b/layout/svg/crashtests/1140080-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<script>
+
+window.addEventListener("load", function() {
+ var stop = document.getElementsByTagName("stop")[0];
+ stop.setAttribute("offset", "0");
+}, false);
+
+</script>
+<stop/>
+</svg>
diff --git a/layout/svg/crashtests/1149542-1.svg b/layout/svg/crashtests/1149542-1.svg
new file mode 100644
index 0000000000..7353f12775
--- /dev/null
+++ b/layout/svg/crashtests/1149542-1.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <style>
+ text { white-space: pre }
+ text::first-letter { color: red; }
+ tspan { display: none }
+ </style>
+ <text textLength="64">
+<tspan>a</tspan>b</text>
+</svg>
diff --git a/layout/svg/crashtests/1156581-1.svg b/layout/svg/crashtests/1156581-1.svg
new file mode 100644
index 0000000000..97e5fb1ca9
--- /dev/null
+++ b/layout/svg/crashtests/1156581-1.svg
@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" style="filter: url(#a); clip: rect(0px, 4rem, 2px, 2px);">
+ <script>
+ function boom()
+ {
+ document.getElementById("a").style.overflow = "hidden";
+ document.documentElement.style.fontSize = "10px";
+ }
+ window.addEventListener("load", boom, false);
+ </script>
+
+ <set id="a"/>
+</svg>
diff --git a/layout/svg/crashtests/1182496-1.html b/layout/svg/crashtests/1182496-1.html
new file mode 100644
index 0000000000..1d95905a2d
--- /dev/null
+++ b/layout/svg/crashtests/1182496-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script>
+ function tweak(){
+ document.body.innerHTML="fuzz"
+ }
+ </script>
+</head>
+<body onload="tweak()">
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <text>
+ <foreignObject requiredFeatures="foo">
+ <svg style="position: absolute;"/>
+ </foreignObject>
+ </text>
+ </svg>
+</body>
+</html>
+
+
diff --git a/layout/svg/crashtests/1209525-1.svg b/layout/svg/crashtests/1209525-1.svg
new file mode 100644
index 0000000000..21134df33b
--- /dev/null
+++ b/layout/svg/crashtests/1209525-1.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ width="0" height="0">
+
+ <rect width="10" height="10" stroke="black"
+ vector-effect="non-scaling-stroke" />
+
+</svg>
diff --git a/layout/svg/crashtests/1223281-1.svg b/layout/svg/crashtests/1223281-1.svg
new file mode 100644
index 0000000000..b548a9a600
--- /dev/null
+++ b/layout/svg/crashtests/1223281-1.svg
@@ -0,0 +1,24 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<script>
+<![CDATA[
+
+function forceFrameConstruction()
+{
+ document.documentElement.getBoundingClientRect();
+}
+
+function boom()
+{
+ document.documentElement.style.overflow = "scroll";
+ forceFrameConstruction()
+ document.documentElement.style.visibility = "visible";
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+
+<rect style="perspective: 10em;" />
+</svg>
+
diff --git a/layout/svg/crashtests/1234726-1.svg b/layout/svg/crashtests/1234726-1.svg
new file mode 100644
index 0000000000..ec807e2aba
--- /dev/null
+++ b/layout/svg/crashtests/1234726-1.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<script>
+
+window.addEventListener("load", function() {
+ document.documentElement.style.fontSize = "70%";
+ document.documentElement.setAttribute("transform", "scale(2)");
+ var filt = document.createElementNS("http://www.w3.org/2000/svg", "filter");
+ filt.style.borderWidth = "2rem";
+ document.documentElement.appendChild(filt);
+}, false);
+
+</script>
+</svg>
diff --git a/layout/svg/crashtests/1322537-1.html b/layout/svg/crashtests/1322537-1.html
new file mode 100644
index 0000000000..04207db4ad
--- /dev/null
+++ b/layout/svg/crashtests/1322537-1.html
@@ -0,0 +1,2 @@
+<svg>
+<animateMotion path='M8,0l69,97m45,-17592186044414A71,23 46,0,0 16382,98Q50,10 48,72T21,0Z'/> \ No newline at end of file
diff --git a/layout/svg/crashtests/1322537-2.html b/layout/svg/crashtests/1322537-2.html
new file mode 100644
index 0000000000..d0495d79af
--- /dev/null
+++ b/layout/svg/crashtests/1322537-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<head>
+ <script>
+ function go() {
+ var path = document.getElementById("myPath");
+ var len = path.getTotalLength();
+ console.log(len);
+ }
+ </script>
+</head>
+<body onload="go()">
+ <svg>
+ <path id="myPath" d="M45,-17592186044414A71,23 46,0,0 16382,98"/>
+ </svg>
+</body>
diff --git a/layout/svg/crashtests/1322852.html b/layout/svg/crashtests/1322852.html
new file mode 100644
index 0000000000..728ea5c7a9
--- /dev/null
+++ b/layout/svg/crashtests/1322852.html
@@ -0,0 +1,2 @@
+<svg>
+<ellipse stroke='-moz-mac-menushadow'/> \ No newline at end of file
diff --git a/layout/svg/crashtests/1348564.svg b/layout/svg/crashtests/1348564.svg
new file mode 100644
index 0000000000..0cb28948e6
--- /dev/null
+++ b/layout/svg/crashtests/1348564.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+<g style="outline-style:auto;">
+<radialGradient style="transform-box:fill-box;transform:rotatex(15deg);"></radialGradient>
+<rect x="200"></rect>
+</g>
+</svg>
diff --git a/layout/svg/crashtests/1402109.html b/layout/svg/crashtests/1402109.html
new file mode 100644
index 0000000000..81ef78a058
--- /dev/null
+++ b/layout/svg/crashtests/1402109.html
@@ -0,0 +1,11 @@
+<script>
+function jsfuzzer() {
+try { svgvar00012.getStartPositionOfChar(0); } catch(e) { }
+}
+</script>
+<body onload=jsfuzzer()>
+<table>
+<caption>,HM&gt;v((Ndeoi=&amp;</caption>
+<caption>
+<svg>
+<text id="svgvar00012">
diff --git a/layout/svg/crashtests/1402124.html b/layout/svg/crashtests/1402124.html
new file mode 100644
index 0000000000..793b46b9a1
--- /dev/null
+++ b/layout/svg/crashtests/1402124.html
@@ -0,0 +1,10 @@
+<script>
+function jsfuzzer() {
+try { a.getExtentOfChar(0); } catch(e) { }
+}
+</script>
+<body onload=jsfuzzer()>
+<svg>
+<switch>
+<path/>
+<text id="a">
diff --git a/layout/svg/crashtests/1402486.html b/layout/svg/crashtests/1402486.html
new file mode 100644
index 0000000000..af0c72e351
--- /dev/null
+++ b/layout/svg/crashtests/1402486.html
@@ -0,0 +1,12 @@
+<script>
+function jsfuzzer() {
+try { a.getEndPositionOfChar(0); } catch(e) { }
+}
+</script>
+<body onload=jsfuzzer()>
+<svg>
+<switch>
+<text id="a">
+</text>
+<feTurbulence
+<!-- a --> \ No newline at end of file
diff --git a/layout/svg/crashtests/1403656-1.html b/layout/svg/crashtests/1403656-1.html
new file mode 100644
index 0000000000..996562d150
--- /dev/null
+++ b/layout/svg/crashtests/1403656-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="filtered">
+<style>
+ .filtered {
+ filter: url(#notsvg);
+ }
+ #notsvg {
+ float: left;
+ overflow: auto;
+ }
+</style>
+<script>
+function go() {
+ const div = document.createElement('div')
+ div.setAttribute('id', 'notsvg')
+ document.documentElement.appendChild(div)
+}
+</script>
+<body onload="go();" class="filtered">
diff --git a/layout/svg/crashtests/1403656-2.html b/layout/svg/crashtests/1403656-2.html
new file mode 100644
index 0000000000..e4287e933f
--- /dev/null
+++ b/layout/svg/crashtests/1403656-2.html
@@ -0,0 +1,21 @@
+<!-- MUST BE QUIRKS MODE -->
+<style>
+span { top: 0%; }
+</style>
+<script>
+function jsfuzzer() {
+ try { text.setAttribute("font-size", "10px"); } catch(e) { }
+}
+</script>
+<body onload=jsfuzzer()>
+ <div style="overflow: scroll">
+ <span></span>
+ </div>
+ <svg>
+ <tref id="marker">
+ <text id="text"/>
+ </tref>
+ <polyline marker-end="url(#marker)">
+ </polyline>
+ </svg>
+</body>
diff --git a/layout/svg/crashtests/1403656-3.html b/layout/svg/crashtests/1403656-3.html
new file mode 100644
index 0000000000..a5a7632665
--- /dev/null
+++ b/layout/svg/crashtests/1403656-3.html
@@ -0,0 +1,8 @@
+<style>
+* { columns: 0px }
+</style>
+A
+<svg id="a">
+<line marker-end="url(#a)">
+</svg>
+<keygen>
diff --git a/layout/svg/crashtests/1403656-4.html b/layout/svg/crashtests/1403656-4.html
new file mode 100644
index 0000000000..1da8979aa7
--- /dev/null
+++ b/layout/svg/crashtests/1403656-4.html
@@ -0,0 +1,9 @@
+<style>
+#a {}
+* {
+ text-align: end;
+ writing-mode: sideways-lr;
+ -webkit-filter: url(#a);
+}
+</style>
+<canvas>AAAAAAAAAAAAAAAA</canvas><ol id="a">@
diff --git a/layout/svg/crashtests/1403656-5.html b/layout/svg/crashtests/1403656-5.html
new file mode 100644
index 0000000000..3d64bc5b50
--- /dev/null
+++ b/layout/svg/crashtests/1403656-5.html
@@ -0,0 +1,11 @@
+<style>
+* {
+ -webkit-filter: url(#a);
+ padding-right: 1vmin;
+ overflow-y: scroll;
+}
+</style>
+<details id="a"></details>
+<image style="bottom: 1em;display:flex"></image>
+<canvas height="1">
+<font>
diff --git a/layout/svg/crashtests/1404086.html b/layout/svg/crashtests/1404086.html
new file mode 100644
index 0000000000..df2ad6a3b3
--- /dev/null
+++ b/layout/svg/crashtests/1404086.html
@@ -0,0 +1,2 @@
+<svg>
+<text textLength="0" lengthAdjust="spacingAndGlyphs">t
diff --git a/layout/svg/crashtests/1421807-1.html b/layout/svg/crashtests/1421807-1.html
new file mode 100644
index 0000000000..fe5a3e8d66
--- /dev/null
+++ b/layout/svg/crashtests/1421807-1.html
@@ -0,0 +1,5 @@
+<body onload=b.appendChild(a)>
+<div id="a" style="display: contents">
+</div>
+<svg>
+<text id="b">
diff --git a/layout/svg/crashtests/1421807-2.html b/layout/svg/crashtests/1421807-2.html
new file mode 100644
index 0000000000..30b100642c
--- /dev/null
+++ b/layout/svg/crashtests/1421807-2.html
@@ -0,0 +1,15 @@
+<style>
+.c1 { display: contents; }
+</style>
+<script>
+function go() {
+ a.attachShadow({mode: "open"}).innerHTML = `<slot> </slot> `;
+ b.appendChild(a);
+}
+</script>
+<body onload=go()>
+<div id="a" class="c1">
+ <span></span>
+</div>
+<svg>
+<text id="b">
diff --git a/layout/svg/crashtests/1422226.html b/layout/svg/crashtests/1422226.html
new file mode 100644
index 0000000000..5826e60b69
--- /dev/null
+++ b/layout/svg/crashtests/1422226.html
@@ -0,0 +1,39 @@
+<style id="htmlvar00001">
+* {
+ filter: saturate(1) hue-rotate(0deg);
+ marker-mid: url()
+}
+</style>
+<script>
+function jsfuzzer() {
+try { var var00078 = window.find("foo",true,true); } catch(e) { }
+try { htmlvar00004.setAttribute("onselect", "eventhandler2()"); } catch(e) { }
+try { htmlvar00004.selectionStart = 1; } catch(e) { }
+try { svgvar00030.addEventListener("DOMSubtreeModified", eventhandler5); } catch(e) { }
+}
+function eventhandler1() {
+try { var var00098 = document.createElement("select"); } catch(e) { }
+try { htmlvar00001.appendChild(var00098); } catch(e) { }
+}
+function eventhandler2() {
+try { htmlvar00015.href = "3" } catch(e) { }
+try { svgvar00008.before(svgvar00013); } catch(e) { }
+try { var var00014 = window.getSelection(); } catch(e) { }
+try { var00014.setBaseAndExtent(htmlvar00011,0,htmlvar00010,0); } catch(e) { }
+try { var00014.collapseToStart(); } catch(e) { }
+}
+function eventhandler5() {
+try { svgvar00001.addEventListener("DOMNodeRemoved", eventhandler1); } catch(e) { }
+}
+</script>
+<body onload=jsfuzzer()>
+<input id="htmlvar00004">
+<svg id="svgvar00001">
+<marker>
+<meshgradient id="svgvar00008">
+<foreignObject id="svgvar00013">
+<discard id="svgvar00030"/>
+<a id="htmlvar00010" contenteditable="true"></a>
+<a id="htmlvar00011">
+<base id="htmlvar00015">
+
diff --git a/layout/svg/crashtests/1425434-1.html b/layout/svg/crashtests/1425434-1.html
new file mode 100644
index 0000000000..cec773ca25
--- /dev/null
+++ b/layout/svg/crashtests/1425434-1.html
@@ -0,0 +1,59 @@
+<html>
+<head>
+<style>
+
+#htmlvar00002,.class4,strong:last-of-type {
+ border-image-repeat: round stretch;
+ counter-increment:c
+}
+.class6,#htmlvar00001,#htmlvar00008 {
+ height:63%;
+ grid-column:span 0 middle / middle
+}
+#htmlvar00002 {
+ overflow-y:hidden;
+ grid-column-end:0 inherit
+}
+
+</style>
+<script>
+
+function eventhandler5() {
+
+ var var00066 = document.createElement("audio");
+ try { var00066.controls = true; } catch(e) { }
+
+ try { document.all['htmlvar00002'].appendChild(htmlvar00001); } catch(e) { }
+ try { document.all['htmlvar00002'].appendChild(var00066); } catch(e) { }
+}
+
+</script>
+</head>
+<body onload=eventhandler5()>
+
+ <shadow id="htmlvar00001" axis="PDcIvMde3=%- S^@dz" ondblclick="eventhandler1()" item="8z6h" checked="checked" type="text/x-javascript">
+ aaa
+ </shadow>
+
+ <ul id="htmlvar00002" type="video/mp4; codecs='avc1.4D400C'" type="turbulence" type="javascript_1.0"
+ role="complementary" dir="ltr" required="required" formnovalidate="formnovalidate" scheme="NIST" expanded="true" slot="slot2">
+ bbb
+ </ul>
+
+ <svg id="svgvar00001" marker-end="url(#svgvar00005)" transform="translate(0, 0) scale(0.113899652038) rotate(1) translate(0, 1)"
+ marker-mid="url(#svgvar00009)" xml:id="test-title" viewBox="0 1 78 1" click="none" glyph-orientation-vertical="-1" dominant-baseline="middle" ry="61%" k="1">
+
+ <path id="svgvar00009" d="M 0 0 L 1 0" stroke-dashoffset="inherit" onclick="eventhandler5()" stroke-linejoin="inherit"
+ fill-rule="evenodd" onmouseover="eventhandler3()" onfocusin="eventhandler4()" pointsAtY="1" target="_self" primitiveUnits="userSpaceOnUse" horiz-adv-x="0" />
+
+ <line id="svgvar00010" x1="0px" y1="55%" x2="76%" y2="62%" transform="translate(0 100) scale(0 -1)"
+ transform="translate(00.32347924876,0)" visibility="hidden" stroke-width="inherit" pointer-events="painted"
+ xlink:type="simple" onrepeat="eventhandler4()" clip-path="url(#svgvar00001)" vector-effect="non-scaling-stroke" azimuth="-1">
+ ccc
+ </line>
+
+ </svg>
+
+<!--endhtml-->
+</body>
+</html>
diff --git a/layout/svg/crashtests/1443092-helper.svg b/layout/svg/crashtests/1443092-helper.svg
new file mode 100644
index 0000000000..134cbefb58
--- /dev/null
+++ b/layout/svg/crashtests/1443092-helper.svg
@@ -0,0 +1,6 @@
+<svg class='class2' xmlns='http://www.w3.org/2000/svg'>
+<clipPath clipPathUnits='userSpaceOnUse' id='id9' lighting-color='pink' class='class0 class1' >
+</clipPath>
+<a id='id0' clip-path='url(#id9)' >
+</a>
+</svg>
diff --git a/layout/svg/crashtests/1443092.html b/layout/svg/crashtests/1443092.html
new file mode 100644
index 0000000000..7be4561e61
--- /dev/null
+++ b/layout/svg/crashtests/1443092.html
@@ -0,0 +1,34 @@
+<script>
+function start() {
+ o7=document.createElement('div');
+ o7.innerHTML='<style>@keyframes key7{ from{ transform: rotate(-536870911deg)}}\n*{ animation-name: key7; animation-duration: 0.001s';
+ o17=document.createElement('div');
+ o17.innerHTML='<svg><style>@font-face{}\n*{ outline-style: dotted</style><style>@font-face{ font-family: font3; src: url(';
+ o18=o17.firstChild.getElementsByTagName('*');
+ o20=o18[0];
+ o23=o18[1];
+ o114=document.createElement('iframe');
+ o114.src='1443092-helper.svg';
+ o114.addEventListener('load', fun0,false);
+ document.body.appendChild(o114);
+}
+function fun0() {
+ o117=o114.contentDocument;
+ document.replaceChild(o117.documentElement,document.documentElement);
+ o124=document.createElement('iframe');
+ document.documentElement.appendChild(o124);
+ o145=document.createElement('div');
+ o145.innerHTML='<svg><set attributeName="text-decoration">';
+ document.documentElement.appendChild(o20);
+ document.documentElement.appendChild(o23);
+ document.documentElement.appendChild(o7);
+ o124.src='x';
+ document.documentElement.appendChild(o145);
+ o264=document.createElement('style');
+ o265=document.createTextNode('*{ float: left');
+ o264.appendChild(o265);
+ o23.appendChild(o264);
+ setTimeout("location.reload()",40);
+}
+</script>
+<body onload="start()"></body>
diff --git a/layout/svg/crashtests/1454201-1.html b/layout/svg/crashtests/1454201-1.html
new file mode 100644
index 0000000000..b92a7aa595
--- /dev/null
+++ b/layout/svg/crashtests/1454201-1.html
@@ -0,0 +1,52 @@
+<html>
+<head>
+<script>
+function loader() {
+var tbody = document.createElement("tbody");
+try { document.all[90%document.all.length].appendChild(tbody); } catch(e) { }
+}
+</script>
+</head>
+<body onload=loader()>
+<span>
+<a></a>
+</span>
+<a></a>
+<svg marker-end="url(#poly)">
+<a></a>
+<a></a>
+<symbol >
+<a></a>
+</symbol>
+<polyline id="poly">
+<a></a>
+<a></a>
+<a></a>
+<a></a>
+</polyline>
+<line >
+<a></a>
+</line>
+<a></a>
+<span>
+<a></a>
+</span>
+<a></a>
+<a></a>
+<a></a>
+</svg>
+<dialog style="-moz-appearance: checkbox; overflow-x: auto; max-width: 0em;" open="true"></dialog>
+<a></a>
+<a></a>
+<span>
+<a></a>
+<a></a>
+<a></a>
+<a></a>
+</span>
+<a></a>
+<a></a>
+<a></a>
+<a></a>
+</body>
+</html>
diff --git a/layout/svg/crashtests/1467552-1.html b/layout/svg/crashtests/1467552-1.html
new file mode 100644
index 0000000000..df85665c72
--- /dev/null
+++ b/layout/svg/crashtests/1467552-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<html>
+<style>
+fieldset {
+ mask: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLz4=),
+ padding-box;
+ margin-right: 75px;
+}
+span {
+ vertical-align: 672in;
+}
+</style>
+<fieldset><span><video></video></span></fieldset>
+</html>
diff --git a/layout/svg/crashtests/1474982.html b/layout/svg/crashtests/1474982.html
new file mode 100644
index 0000000000..c33e2be474
--- /dev/null
+++ b/layout/svg/crashtests/1474982.html
@@ -0,0 +1,2 @@
+<svg>
+<text lengthAdjust="" textLength="0">A</text>
diff --git a/layout/svg/crashtests/1480224.html b/layout/svg/crashtests/1480224.html
new file mode 100644
index 0000000000..0bb0dfa7b5
--- /dev/null
+++ b/layout/svg/crashtests/1480224.html
@@ -0,0 +1,6 @@
+<svg>
+<symbol id="a">
+<foreignObject>
+<input type="file">
+</symbol>
+<use xlink:href="#a">
diff --git a/layout/svg/crashtests/1480275.html b/layout/svg/crashtests/1480275.html
new file mode 100644
index 0000000000..b21ebdd062
--- /dev/null
+++ b/layout/svg/crashtests/1480275.html
@@ -0,0 +1,15 @@
+<script>
+function go() {
+ b.appendChild(a);
+ d.outerHTML = f.outerHTML;
+}
+</script>
+<table>
+<tr id="a">
+<th>
+<svg id="c" onload="go()">
+<use xlink:href="#c"/>
+</tr>
+<code id="d"></code>
+<video id="f">
+<details id="b">
diff --git a/layout/svg/crashtests/1502936.html b/layout/svg/crashtests/1502936.html
new file mode 100644
index 0000000000..87892ab461
--- /dev/null
+++ b/layout/svg/crashtests/1502936.html
@@ -0,0 +1,11 @@
+<script>
+function go() {
+ a.setAttribute("requiredExtensions", "x");
+ b.getBoundingClientRect();
+ a.setAttribute("y", "-1px");
+}
+</script>
+<body onload=go()>
+<svg>
+<use id="a">
+<feGaussianBlur id="b">
diff --git a/layout/svg/crashtests/1504072.html b/layout/svg/crashtests/1504072.html
new file mode 100644
index 0000000000..a44e2f0251
--- /dev/null
+++ b/layout/svg/crashtests/1504072.html
@@ -0,0 +1,4 @@
+<style>
+svg { perspective: 0px }
+</style>
+<svg overflow="scroll" systemLanguage="">
diff --git a/layout/svg/crashtests/1504918.svg b/layout/svg/crashtests/1504918.svg
new file mode 100644
index 0000000000..63cd3a08e0
--- /dev/null
+++ b/layout/svg/crashtests/1504918.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <style>textPath { display: contents; }</style>
+ <text>x<textPath><textPath><tspan>y</tspan></textPath></textPath></text>
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/1535517-1.svg b/layout/svg/crashtests/1535517-1.svg
new file mode 100644
index 0000000000..6f3f17eabf
--- /dev/null
+++ b/layout/svg/crashtests/1535517-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<marker><text><tspan id="x"/></text></marker>
+
+<script>
+window.addEventListener("load", function() {
+ document.getElementById("x").appendChild(document.createTextNode("\u062Ax"));
+}, false);
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/1536892.html b/layout/svg/crashtests/1536892.html
new file mode 100644
index 0000000000..8bc237138e
--- /dev/null
+++ b/layout/svg/crashtests/1536892.html
@@ -0,0 +1,13 @@
+<style>
+* { -webkit-filter: blur(5px) }
+</style>
+<script>
+function go() {
+ a.setAttribute("text-decoration", "overline")
+}
+</script>
+<body onload=go()>
+<svg id="a">
+<marker>
+<foreignObject>
+<li style="-webkit-box-shadow:8px 0 1px">
diff --git a/layout/svg/crashtests/1539318-1.svg b/layout/svg/crashtests/1539318-1.svg
new file mode 100644
index 0000000000..d832f448a3
--- /dev/null
+++ b/layout/svg/crashtests/1539318-1.svg
@@ -0,0 +1,10 @@
+<script>
+window.onload = function() {
+ a.getComputedTextLength()
+}
+</script>
+<body>
+<svg>
+<switch>
+<hatch>
+<text id="a">A</text>
diff --git a/layout/svg/crashtests/1548985-1.html b/layout/svg/crashtests/1548985-1.html
new file mode 100644
index 0000000000..7156eaf36b
--- /dev/null
+++ b/layout/svg/crashtests/1548985-1.html
@@ -0,0 +1,16 @@
+<!-- a -->
+<style>
+:root { contain: size }
+</style>
+<script>
+window.requestIdleCallback(window.close)
+function go() {
+ a.appendChild(document.head)
+ let b = d.getRootNode({composed: true})
+ b.replaceChild(c, b.childNodes[1])
+}
+</script>
+<body onload=go()>
+<svg id="c">
+<feTile id="d" />
+<foreignObject id="a">
diff --git a/layout/svg/crashtests/1548985-2.svg b/layout/svg/crashtests/1548985-2.svg
new file mode 100644
index 0000000000..700facb411
--- /dev/null
+++ b/layout/svg/crashtests/1548985-2.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ style="contain:size">
+ <rect width="100px" height="100px" fill="lime"/>
+</svg>
diff --git a/layout/svg/crashtests/1555851.html b/layout/svg/crashtests/1555851.html
new file mode 100644
index 0000000000..2b6f5a596a
--- /dev/null
+++ b/layout/svg/crashtests/1555851.html
@@ -0,0 +1,8 @@
+<body>
+<script>
+ var o0 = document.createElementNS("http://www.w3.org/2000/svg", "svg")
+ document.body.appendChild(o0)
+ var o3 = document.createElementNS("http://www.w3.org/2000/svg", "animateMotion")
+ o0.appendChild(o3)
+ o3.setAttribute("path", "M9,2l2e37,3A40,2 85,102-1")
+</script>
diff --git a/layout/svg/crashtests/1563779.html b/layout/svg/crashtests/1563779.html
new file mode 100644
index 0000000000..4c51d0884d
--- /dev/null
+++ b/layout/svg/crashtests/1563779.html
@@ -0,0 +1,19 @@
+<script id="a">
+let count = 0;
+function go() {
+ if (count++ == 3)
+ return;
+ try { a.appendChild(c) } catch(e) { }
+ try { window.getSelection().getRangeAt(0).insertNode(b) } catch(e) { }
+ try { d.selectSubString(0,-1) } catch(e) { }
+ document.documentElement.style.display = "none"
+ document.documentElement.getBoundingClientRect()
+ document.documentElement.style.display = ""
+}
+</script>
+<pre id="b" style="display:contents">a</pre>
+<span id="c">
+<style onload="go()"></style>
+</span>
+<svg>
+<text id="d"><textPath xml:space="preserve">
diff --git a/layout/svg/crashtests/1600855.html b/layout/svg/crashtests/1600855.html
new file mode 100644
index 0000000000..31c4809ecb
--- /dev/null
+++ b/layout/svg/crashtests/1600855.html
@@ -0,0 +1,8 @@
+<script>
+window.onload = () => {
+ a.append(String.fromCodePoint(71341))
+}
+</script>
+<font style="word-spacing: 31pc">
+<svg>
+<text id="a">Text</text>
diff --git a/layout/svg/crashtests/1601824.html b/layout/svg/crashtests/1601824.html
new file mode 100644
index 0000000000..04d0de825d
--- /dev/null
+++ b/layout/svg/crashtests/1601824.html
@@ -0,0 +1,7 @@
+<svg>
+<polygon id="a" points="0 1">
+</polygon>
+<text>
+<textPath xlink:href="#a">
+<marker />
+<a systemLanguage="">
diff --git a/layout/svg/crashtests/1605223-1.html b/layout/svg/crashtests/1605223-1.html
new file mode 100644
index 0000000000..6b476a7fd0
--- /dev/null
+++ b/layout/svg/crashtests/1605223-1.html
@@ -0,0 +1,4 @@
+<svg filter="url(#a)">
+<filter id="a">
+<feComponentTransfer>
+<feFuncG type="" />
diff --git a/layout/svg/crashtests/1609663.html b/layout/svg/crashtests/1609663.html
new file mode 100644
index 0000000000..c5a5d938e0
--- /dev/null
+++ b/layout/svg/crashtests/1609663.html
@@ -0,0 +1,34 @@
+<html>
+<head>
+</head>
+<body>
+<div id="main">
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="mySvg" width="400" height="800">
+ <defs>
+ <pattern id="pattern1" x="30" y="10" width="30" height="30" patternUnits="userSpaceOnUse" >
+ <text id="idText1" x="15" y="15" font-size="20">1</text>
+ </pattern>
+ </defs>
+ <rect id="idRect1" fill="url(#pattern1)" width="300" height="200"/>
+ </svg>
+</div>
+<script>
+document.body.offsetHeight;
+
+var pattern = document.getElementById('pattern1');
+var text = document.getElementById('idText1');
+pattern.removeChild(text);
+
+var svgNS = "http://www.w3.org/2000/svg";
+var newText = document.createElementNS(svgNS,"text");
+newText.setAttributeNS(null,"id",'idText1');
+newText.setAttributeNS(null,"x",15);
+newText.setAttributeNS(null,"y",15);
+newText.setAttributeNS(null,"font-size","20");
+
+var textNode = document.createTextNode('x');
+newText.appendChild(textNode);
+pattern.appendChild(newText);
+</script>
+</body>
+</html>
diff --git a/layout/svg/crashtests/1671950.html b/layout/svg/crashtests/1671950.html
new file mode 100644
index 0000000000..173ace4ea9
--- /dev/null
+++ b/layout/svg/crashtests/1671950.html
@@ -0,0 +1,27 @@
+<html>
+<head>
+ <style>
+ * {
+ break-before: always ! important;
+ }
+ </style>
+ <script>
+ window.addEventListener('load', () => {
+ const style = document.createElement('style')
+ document.head.appendChild(style)
+ const svg_1 = document.getElementById('id_3')
+ const svg_2 = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
+ const switch_0 = document.createElementNS('http://www.w3.org/2000/svg', 'switch')
+ const c_0 = document.createElementNS('http://www.w3.org/2000/svg', 'c')
+ switch_0.appendChild(c_0)
+ svg_1.appendChild(switch_0)
+ svg_2.appendChild(svg_1)
+ document.documentElement.appendChild(svg_2)
+ style.sheet.insertRule('@-moz-document url-prefix(){*,a{all:inherit', 0)
+ SpecialPowers.wrap(window).printPreview()?.close()
+ })
+ </script>
+ <svg id='id_3'></svg>
+</head>
+</html>
+
diff --git a/layout/svg/crashtests/1678947.html b/layout/svg/crashtests/1678947.html
new file mode 100644
index 0000000000..96a3a37862
--- /dev/null
+++ b/layout/svg/crashtests/1678947.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+<style>
+ol { float: right; }
+</style>
+<script>
+function start() {
+ document.elementFromPoint(0,1);
+ document.dir = "rtl";
+}
+</script>
+</head>
+<body>
+<svg onload="start()" requiredExtensions="x">
+ <g id="a"/></g>
+<text>
+<textPath xlink:href="#a">
+</svg>
+<ol></ol>
+</body>
+</html>
diff --git a/layout/svg/crashtests/1693032.html b/layout/svg/crashtests/1693032.html
new file mode 100644
index 0000000000..49a2bf3c17
--- /dev/null
+++ b/layout/svg/crashtests/1693032.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script>
+ document.addEventListener('DOMContentLoaded', () => {
+ const svg_1 = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
+ const svg_2 = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
+ const switch_1 = document.createElementNS('http://www.w3.org/2000/svg', 'switch')
+ const metadata_1 = document.createElementNS('http://www.w3.org/2000/svg', 'metadata')
+ const foreign_1 = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject')
+ const text_1 = document.createElementNS('http://www.w3.org/2000/svg', 'text')
+ switch_1.appendChild(metadata_1)
+ svg_2.appendChild(text_1)
+ foreign_1.appendChild(svg_2)
+ switch_1.appendChild(foreign_1)
+ svg_1.appendChild(switch_1)
+ document.documentElement.appendChild(svg_1)
+ })
+ </script>
+</head>
+</html>
diff --git a/layout/svg/crashtests/1696505.html b/layout/svg/crashtests/1696505.html
new file mode 100644
index 0000000000..92700496e8
--- /dev/null
+++ b/layout/svg/crashtests/1696505.html
@@ -0,0 +1,9 @@
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+ document.getElementById('a').style.cssText += "grid-row-end:auto"
+})
+</script>
+<svg>
+<polyline marker-mid='url(#a)'/>
+<marker id='a'>
+<text/>
diff --git a/layout/svg/crashtests/1755770-1.html b/layout/svg/crashtests/1755770-1.html
new file mode 100644
index 0000000000..ee9cf6fee7
--- /dev/null
+++ b/layout/svg/crashtests/1755770-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script>
+ window.addEventListener("load", () => {
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
+ text.setAttribute("letter-spacing", "65535pc");
+ const node = document.createTextNode("\rï¿»ð¨\nó = ð£%?;ð©á©¿ð�");
+ text.appendChild(node);
+ svg.appendChild(text);
+ document.documentElement.appendChild(svg);
+ const rect = new DOMRectReadOnly(-128, 256, 0, 1024);
+ node.convertRectFromNode(rect, document, {});
+ })
+ </script>
+</head>
+</html>
diff --git a/layout/svg/crashtests/1755770-2.html b/layout/svg/crashtests/1755770-2.html
new file mode 100644
index 0000000000..31b42ec452
--- /dev/null
+++ b/layout/svg/crashtests/1755770-2.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script>
+ window.addEventListener("load", () => {
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
+ text.setAttribute("letter-spacing", "65535pc");
+ text.setAttribute("writing-mode", "vertical-lr");
+ const node = document.createTextNode("\rï¿»ð¨\nó = ð£%?;ð©á©¿ð�");
+ text.appendChild(node);
+ svg.appendChild(text);
+ document.documentElement.appendChild(svg);
+ const rect = new DOMRectReadOnly(-128, 256, 0, 1024);
+ node.convertRectFromNode(rect, document, {});
+ })
+ </script>
+</head>
+</html>
diff --git a/layout/svg/crashtests/1758029-1.html b/layout/svg/crashtests/1758029-1.html
new file mode 100644
index 0000000000..2097e875ee
--- /dev/null
+++ b/layout/svg/crashtests/1758029-1.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<style>
+ body { background: gray; }
+ canvas { border: 2px solid black;}
+</style>
+
+<img id="img"
+ onload="go()"
+ src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==">
+<canvas id="canvas"></canvas>
+<script>
+ const ctx = canvas.getContext("2d", { desynchronized: true });
+ const SVG_FILTER = `
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <filter id="posterize">
+ <feComponentTransfer>
+ <feFuncR type="discrete" tableValues="0,1" />
+ <feFuncG type="discrete" tableValues="0,1" />
+ <feFuncB type="discrete" tableValues="0,1" />
+ <feFuncA type="discrete" tableValues="0,1" />
+ </feComponentTransfer>
+ </filter>
+ </svg>`;
+
+ const FILTER1 = `url('data:image/svg+xml;utf8,${SVG_FILTER.replace(/\n/g, "")
+ .replace(/\s+/g, " ")
+ .trim()}#posterize') grayscale(50%) brightness(50%)`;
+ function go() {
+ canvas.width = img.naturalWidth;
+ canvas.height = img.naturalHeight;
+
+ ctx.imageSmoothingEnabled = true;
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ ctx.filter = FILTER1;
+ ctx.drawImage(img, 0, 0);
+ setTimeout(() => { document.documentElement.removeAttribute("class")}, 0);
+ }
+</script>
diff --git a/layout/svg/crashtests/1764936-1.html b/layout/svg/crashtests/1764936-1.html
new file mode 100644
index 0000000000..c03789d547
--- /dev/null
+++ b/layout/svg/crashtests/1764936-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script>
+ window.addEventListener("load", () => {
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
+ const image = document.createElementNS("http://www.w3.org/2000/svg", "image")
+ image.setAttribute("height", "78.04250580135444ch")
+ image.setAttribute("width", "1024rem")
+ image.setAttribute("clip-path", "path( evenodd, '\\C' )")
+ svg.appendChild(image)
+ document.documentElement.appendChild(svg)
+ })
+ </script>
+</head>
+</html>
diff --git a/layout/svg/crashtests/1804958.html b/layout/svg/crashtests/1804958.html
new file mode 100644
index 0000000000..663f621d4c
--- /dev/null
+++ b/layout/svg/crashtests/1804958.html
@@ -0,0 +1,4 @@
+<svg>
+<polygon points="0,2 2,0" marker-end="url(#a)"></polygon>
+<marker id="a" clip-path="">
+<svg style="mix-blend-mode: lighten">
diff --git a/layout/svg/crashtests/1810260.html b/layout/svg/crashtests/1810260.html
new file mode 100644
index 0000000000..f555581165
--- /dev/null
+++ b/layout/svg/crashtests/1810260.html
@@ -0,0 +1,28 @@
+<style>
+*:only-child {
+ display: contents;
+}
+*:nth-child(1) {
+ mask: url(data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=) repeat-x view-box;
+}
+</style>
+<script>
+document.addEventListener('DOMContentLoaded', () => {
+ document.execCommand("selectAll", false)
+ document.designMode = "on"
+ document.execCommand("backColor", false, "red")
+ document.execCommand("justifyFull", false)
+})
+</script>
+A
+<q contenteditable="true">
+<link>
+<svg>
+<line></line>
+<text white-space="pre-line">
+A
+</text>
+<defs>
+</svg>
+A
+
diff --git a/layout/svg/crashtests/220165-1.svg b/layout/svg/crashtests/220165-1.svg
new file mode 100644
index 0000000000..0335f78d41
--- /dev/null
+++ b/layout/svg/crashtests/220165-1.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml" height="500"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="document.documentElement.getBoundingClientRect();
+ document.getElementById('x').textContent = 'New text'">
+
+ <foreignObject x="200" y="180" width="100" height="50" >
+ <html:button id="x">Old long long long text</html:button>
+ </foreignObject>
+
+ <g transform="rotate(10) translate(-100) scale(0.8)">
+ <polygon style="fill:red; fill-opacity:0.5;"
+ points="350, 75 379,161 469,161 397,215
+ 423,301 350,250 277,301 303,215
+ 231,161 321,161" />
+
+ </g>
+
+</svg>
diff --git a/layout/svg/crashtests/267650-1.svg b/layout/svg/crashtests/267650-1.svg
new file mode 100644
index 0000000000..3e9c7ecc01
--- /dev/null
+++ b/layout/svg/crashtests/267650-1.svg
@@ -0,0 +1,4 @@
+<?xml version='1.0'?>
+<svg xmlns='http://www.w3.org/2000/svg'>
+ <text fill='none' stroke='black'>TESTCASE</text>
+</svg>
diff --git a/layout/svg/crashtests/294022-1.svg b/layout/svg/crashtests/294022-1.svg
new file mode 100644
index 0000000000..f30b484c83
--- /dev/null
+++ b/layout/svg/crashtests/294022-1.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ >
+
+ <g>
+ <clipPath id="action_box_cp">
+ <rect width="100" height="46"/>
+ </clipPath>
+ <text style="clip-path:url(#action_box_cp); " y="10" id="action_boxtext" pointer-events="none" class="TextBoxText">
+ <tspan x="0" dy="0">Action</tspan>
+ </text>
+ </g>
+
+</svg>
diff --git a/layout/svg/crashtests/307314-1.svg b/layout/svg/crashtests/307314-1.svg
new file mode 100644
index 0000000000..5c538df88d
--- /dev/null
+++ b/layout/svg/crashtests/307314-1.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait" onload="setTimeout(function() { var g = document.getElementById('N1'), h = document.getElementById('N2'); g.appendChild(h); document.documentElement.removeAttribute("class"); }, 20);">
+
+ <text id="N1"/>
+ <text id="N2">
+ <tspan>
+ <textPath/>
+ </tspan>
+ </text>
+</svg>
diff --git a/layout/svg/crashtests/308615-1.svg b/layout/svg/crashtests/308615-1.svg
new file mode 100644
index 0000000000..fe5391de38
--- /dev/null
+++ b/layout/svg/crashtests/308615-1.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <pattern id="pattern1">
+ <rect style="fill:url(#pattern1);"/>
+ </pattern>
+
+ <rect style="fill:url(#pattern1);" />
+
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/308917-1.svg b/layout/svg/crashtests/308917-1.svg
new file mode 100644
index 0000000000..7d0ae44f74
--- /dev/null
+++ b/layout/svg/crashtests/308917-1.svg
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<svg version="1.1" baseProfile="basic" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 480 360" class="reftest-wait" onload="first();">
+
+<script><![CDATA[
+
+function first()
+{
+ document.getElementById("z").appendChild(document.getElementById("pat1"));
+ setTimeout(second, 30);
+}
+
+function second()
+{
+ document.getElementById("pat4").appendChild(document.getElementById("z"));
+ document.documentElement.removeAttribute("class");
+}
+
+]]></script>
+
+
+ <pattern patternUnits="userSpaceOnUse" id="pat1" x="10" y="10" width="20" height="20">
+ <rect x="5" y="5" width="10" height="10" fill="red" />
+ <rect x="10" y="10" width="10" height="10" fill="green" />
+ </pattern>
+ <rect x="25" y="10" width="430" height="60" stroke="black" fill="url(#pat1)" />
+
+ <pattern patternUnits="userSpaceOnUse" id="pat4" x="0" y="0" width="20" height="10">
+ <rect x="0" y="0" width="10" height="10" fill="red" />
+ <rect x="10" y="0" width="10" height="10" fill="blue" />
+ </pattern>
+ <text font-family="Arial" font-size="40" fill="none" stroke="url(#pat4)" stroke-width="2" x="25" y="275" id="z">Pattern on stroke</text>
+
+</svg>
+
diff --git a/layout/svg/crashtests/310436-1.svg b/layout/svg/crashtests/310436-1.svg
new file mode 100644
index 0000000000..e6dd5680ce
--- /dev/null
+++ b/layout/svg/crashtests/310436-1.svg
@@ -0,0 +1,28 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait"><script><![CDATA[
+
+function init() {
+ var docElt = document.documentElement;
+ var div1 = document.getElementById("div1");
+ var div2 = document.getElementById("div2");
+ var textNode = div2.childNodes[0];
+
+ function first()
+ {
+ docElt.appendChild(div2);
+ div2.appendChild(div1);
+ }
+
+ function second()
+ {
+ div2.appendChild(div1);
+ div1.appendChild(textNode);
+ document.documentElement.removeAttribute("class");
+ }
+
+ first();
+ setTimeout(second, 30);
+}
+
+window.addEventListener("load", init, false);
+
+]]></script><div xmlns="http://www.w3.org/1999/xhtml" id="div1"><div id="div2">A Z</div></div></svg>
diff --git a/layout/svg/crashtests/310638.svg b/layout/svg/crashtests/310638.svg
new file mode 100644
index 0000000000..e5ee30fb2c
--- /dev/null
+++ b/layout/svg/crashtests/310638.svg
@@ -0,0 +1,35 @@
+<svg xmlns="http://www.w3.org/2000/svg"><div xmlns='http://www.w3.org/1999/xhtml' id="div1">
+<div id="div2">bar</div>
+</div>
+<script><![CDATA[
+
+function init()
+{
+ var div2 = document.getElementById("div2");
+ var div1 = document.getElementById("div1");
+ var docElt = document.documentElement;
+ var titleText = document.createTextNode("foo baz");
+
+ docElt.appendChild(div2); div2.appendChild(titleText);
+
+ function second ()
+ {
+ div2.appendChild(div1);
+ removeNode(titleText);
+ removeNode(div2);
+ }
+
+ setTimeout(second, 0);
+}
+
+
+function removeNode(q1) { q1.parentNode.removeChild(q1); }
+
+
+setTimeout(init, 0);
+
+
+]]></script>
+
+
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/313737-1.xml b/layout/svg/crashtests/313737-1.xml
new file mode 100644
index 0000000000..93421f6077
--- /dev/null
+++ b/layout/svg/crashtests/313737-1.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
+<!DOCTYPE xhtml PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd" [
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <head>
+ <title>bug 313737</title>
+ </head>
+ <body>
+
+ <svg:svg style="position:fixed;">
+ <input type="text" style="position:absolute;" />
+ </svg:svg>
+
+ </body>
+</html>
diff --git a/layout/svg/crashtests/314244-1.xhtml b/layout/svg/crashtests/314244-1.xhtml
new file mode 100644
index 0000000000..14717f146d
--- /dev/null
+++ b/layout/svg/crashtests/314244-1.xhtml
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=314244 -->
+<!-- Just checking for lack of crash, nothing more -->
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="GMail"
+ width="300" height="300"
+ screenX="10" screenY="10">
+
+ <hbox>
+ <svg version="1.0"
+ xmlns="http://www.w3.org/2000/svg"
+ width="100px" height="100px"
+ id="back-button"
+ class="nav-button"
+ style="display: -moz-box;">
+ <rect x="10" y="10" width="80" height="80" fill="blue" />
+ </svg>
+ <spacer flex="1" />
+ </hbox>
+
+ <spacer flex="1" />
+
+</window>
+
diff --git a/layout/svg/crashtests/322185-1.svg b/layout/svg/crashtests/322185-1.svg
new file mode 100644
index 0000000000..8a10eb34a4
--- /dev/null
+++ b/layout/svg/crashtests/322185-1.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+ <g id="g" style="display: -moz-box; overflow: hidden;">
+ <circle />
+ </g>
+</svg>
diff --git a/layout/svg/crashtests/322215-1.svg b/layout/svg/crashtests/322215-1.svg
new file mode 100644
index 0000000000..f872fbcd8f
--- /dev/null
+++ b/layout/svg/crashtests/322215-1.svg
@@ -0,0 +1,31 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- 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/. -->
+<!--
+Copyright Georgi Guninski
+-->
+
+
+<svg width="100%" height="100%" version="1.1"
+xmlns="http://www.w3.org/2000/svg">
+
+<defs>
+<filter id="MyFilter" filterUnits="userSpaceOnUse"
+x="0" y="0" width="32769" height="32769">
+
+<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/>
+
+</filter>
+</defs>
+
+<rect x="1" y="1" width="198" height="118" fill="#cccccc" />
+
+<g filter="url(#MyFilter)">
+<text fill="#FFFFFF" stroke="black" font-size="45"
+x="42" y="42">Feck b1ll</text>
+</g>
+
+</svg>
diff --git a/layout/svg/crashtests/323704-1.svg b/layout/svg/crashtests/323704-1.svg
new file mode 100644
index 0000000000..13b8d52243
--- /dev/null
+++ b/layout/svg/crashtests/323704-1.svg
@@ -0,0 +1,12 @@
+<svg xmlns='http://www.w3.org/2000/svg'
+xmlns:xlink='http://www.w3.org/1999/xlink'>
+
+ <clipPath id='clipPath_0'>
+ <rect x='10' y='10' width='25' height='25' rx='5' ry='5' fill='none'
+clip-path='url(#clipPath_0)'/>
+ </clipPath>
+
+ <rect x='5' y='5' width='35' height='35' fill='red'
+clip-path='url(#clipPath_0)'/>
+
+</svg>
diff --git a/layout/svg/crashtests/325427-1.svg b/layout/svg/crashtests/325427-1.svg
new file mode 100644
index 0000000000..1f1c645251
--- /dev/null
+++ b/layout/svg/crashtests/325427-1.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <g id="module">
+ <use xlink:href="#module" />
+ <use xlink:href="#module" />
+ <use xlink:href="#module" />
+ <use xlink:href="#module" />
+ <use xlink:href="#baseModule" />
+ <use xlink:href="#baseModule" />
+ <use xlink:href="#extendsModule" />
+ <use xlink:href="#extendsModule" />
+ <use xlink:href="#extendsModule" />
+ <use xlink:href="#extendsModule" />
+ <use xlink:href="#extendsModule" />
+ <use xlink:href="#extendsModule" />
+ </g>
+ <use id="baseModule" xlink:href="#module" />
+ <use id="extendsModule" xlink:href="#module" />
+ </defs>
+</svg>
diff --git a/layout/svg/crashtests/326495-1.svg b/layout/svg/crashtests/326495-1.svg
new file mode 100644
index 0000000000..a5bf25b62a
--- /dev/null
+++ b/layout/svg/crashtests/326495-1.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<svg width="10cm" height="5cm" viewBox="0 0 1000 500"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1">
+
+ <script>
+ function init()
+ {
+ document.getElementsByTagName("rect")[0].style.display = "-moz-inline-box";
+ }
+
+ window.addEventListener("load", init, false);
+ </script>
+
+ <rect requiredFeatures="http://www.w3.org/TR/SVG11/feature#SVG-nonexistent-feature"/>
+</svg>
diff --git a/layout/svg/crashtests/326974-1.svg b/layout/svg/crashtests/326974-1.svg
new file mode 100644
index 0000000000..750165b730
--- /dev/null
+++ b/layout/svg/crashtests/326974-1.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<script>
+
+function init() { var n2 = document.getElementById("n2");
+ var n3 = document.getElementById("n3");
+
+ n2.appendChild(n3);
+}
+
+window.addEventListener("load", init, false);
+
+</script>
+
+
+<g id="n2"> <text id="n3" />
+</g>
+
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/327706-1.svg b/layout/svg/crashtests/327706-1.svg
new file mode 100644
index 0000000000..9aae909250
--- /dev/null
+++ b/layout/svg/crashtests/327706-1.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<script>
+
+document.documentElement.unsuspendRedraw(6)
+
+</script>
+</svg>
diff --git a/layout/svg/crashtests/327711-1.svg b/layout/svg/crashtests/327711-1.svg
new file mode 100644
index 0000000000..d919b866f6
--- /dev/null
+++ b/layout/svg/crashtests/327711-1.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<script>
+
+function init()
+{
+ document.documentElement.unsuspendRedrawAll();
+ document.getElementsByTagName("text")[0].firstChild.data = "Quux";
+}
+
+window.addEventListener("load", init, false);
+
+</script>
+
+<text x="125" y="30">Foo</text>
+
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/328137-1.svg b/layout/svg/crashtests/328137-1.svg
new file mode 100644
index 0000000000..26190b1eb0
--- /dev/null
+++ b/layout/svg/crashtests/328137-1.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<script>
+
+
+function init()
+{
+ x = document.getElementsByTagName("stop");
+ x[0].appendChild(x[1]);
+}
+
+
+window.addEventListener("load", init, false);
+
+</script>
+
+<radialGradient>
+ <stop/>
+ <stop/>
+</radialGradient>
+
+</svg>
diff --git a/layout/svg/crashtests/329848-1.svg b/layout/svg/crashtests/329848-1.svg
new file mode 100644
index 0000000000..ac4f0022e1
--- /dev/null
+++ b/layout/svg/crashtests/329848-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"> <polygon transform="?" points="100,100 200,100 150,200"/> </svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/337408-1.xhtml b/layout/svg/crashtests/337408-1.xhtml
new file mode 100644
index 0000000000..f56c06ec94
--- /dev/null
+++ b/layout/svg/crashtests/337408-1.xhtml
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=337408 -->
+<!-- Just checking for lack of crash, nothing more -->
+<window id="svg-in-xul-stack"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ style="background-color:white;"
+ screenX="20"
+ screenY="20"
+ width="600"
+ height="400">
+
+ <stack>
+ <box flex="1">
+ <label value="foo"/>
+ </box>
+ <svg:svg>
+ <svg:rect width="100%" height="100%" fill="red" fill-opacity="0.5"/>
+ </svg:svg>
+ </stack>
+</window>
diff --git a/layout/svg/crashtests/338301-1.xhtml b/layout/svg/crashtests/338301-1.xhtml
new file mode 100644
index 0000000000..3367eedebb
--- /dev/null
+++ b/layout/svg/crashtests/338301-1.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de">
+<body>
+
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient>
+ <path/>
+ </linearGradient>
+ </defs>
+ </svg>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/338312-1.xhtml b/layout/svg/crashtests/338312-1.xhtml
new file mode 100644
index 0000000000..5a751a2ae0
--- /dev/null
+++ b/layout/svg/crashtests/338312-1.xhtml
@@ -0,0 +1,28 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+
+function boom()
+{
+ document.getElementById("foo").appendChild(document.getElementById("bar"));
+}
+
+window.addEventListener("load", boom, false);
+
+</script>
+</head>
+
+<body>
+
+ <div id="foo"></div>
+
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="grad1"/>
+ </defs>
+ <rect id="bar" style="fill:url(#grad1);" />
+ </svg>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/340083-1.svg b/layout/svg/crashtests/340083-1.svg
new file mode 100644
index 0000000000..7f015b6efe
--- /dev/null
+++ b/layout/svg/crashtests/340083-1.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%">
+ <defs>
+ <title>
+ <image x="30" y="0" width="190" height="190" xlink:href="../../../testing/crashtest/images/tree.gif"/>
+ </title>
+ </defs>
+</svg>
diff --git a/layout/svg/crashtests/340945-1.svg b/layout/svg/crashtests/340945-1.svg
new file mode 100644
index 0000000000..01ac66fb33
--- /dev/null
+++ b/layout/svg/crashtests/340945-1.svg
@@ -0,0 +1,2 @@
+<svg xmlns="http://www.w3.org/2000/svg" style="display: table;">
+</svg>
diff --git a/layout/svg/crashtests/342923-1.html b/layout/svg/crashtests/342923-1.html
new file mode 100644
index 0000000000..bed6e89791
--- /dev/null
+++ b/layout/svg/crashtests/342923-1.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+<script>
+
+function boo()
+{
+ var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
+ rect.setAttribute("stroke", "blue");
+
+ document.body.appendChild(rect);
+}
+
+</script>
+</head>
+
+<body class="bodytext" onload="boo();">
+
+<div id="c1"></div>
+
+<p>In a debug trunk build from 2006-006-27, loading this page triggers an assertion. (It also triggers a CSS error in the console, but I think that's a known, separate bug.)</p>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/343221-1.xhtml b/layout/svg/crashtests/343221-1.xhtml
new file mode 100644
index 0000000000..890b161dfe
--- /dev/null
+++ b/layout/svg/crashtests/343221-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+
+function boo()
+{
+ document.getElementById("c").style.overflow = "hidden";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="setTimeout(boo, 30);">
+
+<svg xmlns="http://www.w3.org/2000/svg">
+ <circle id="c" cx="50" cy="50" r="20" />
+</svg>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/344749-1.svg b/layout/svg/crashtests/344749-1.svg
new file mode 100644
index 0000000000..1a02d7c180
--- /dev/null
+++ b/layout/svg/crashtests/344749-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+
+<circle cx="6cm" cy="2cm" r="100" fill="red" transform="translate(0,50)" />
+<circle cx="6cm" cy="2cm" r="100" fill="blue" transform="translate(70,150)" />
+<circle cx="6cm" cy="2cm" r="100" fill="green" transform="translate(-70,150)" />
+
+<rect id="rect1" fill="url(#pat0)"/>
+
+<pattern patternUnits="userSpaceOnUse" id="pat0" x="10" y="10" width="20" height="20"> <rect x="5" y="5" width="10" height="10" fill="red" /> <rect x="10" y="10" width="10" height="10" fill="green" /> </pattern>
+ </svg>
diff --git a/layout/svg/crashtests/344887-1.svg b/layout/svg/crashtests/344887-1.svg
new file mode 100644
index 0000000000..f0bd21c592
--- /dev/null
+++ b/layout/svg/crashtests/344887-1.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" onload="setTimeout(foo, 30);" class="reftest-wait">
+
+<script>
+
+var SVG_NS = "http://www.w3.org/2000/svg";
+
+function foo()
+{
+ var rect = document.createElementNS(SVG_NS, 'rect');
+ rect.setAttribute('opacity', ".3");
+ document.documentElement.appendChild(rect);
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/344892-1.svg b/layout/svg/crashtests/344892-1.svg
new file mode 100644
index 0000000000..a38d7eb40f
--- /dev/null
+++ b/layout/svg/crashtests/344892-1.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<text stroke-width="50%">foo</text>
+
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/344898-1.svg b/layout/svg/crashtests/344898-1.svg
new file mode 100644
index 0000000000..34c3f45a4e
--- /dev/null
+++ b/layout/svg/crashtests/344898-1.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg" onload="setTimeout(removeText, 30);" class="reftest-wait">
+
+
+<script>
+
+function removeText()
+{
+ var x = document.getElementById("textPath");
+ x.removeChild(x.firstChild);
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+
+<text x="30" y="30"><textPath id="textPath">Foo</textPath></text>
+
+
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/344904-1.svg b/layout/svg/crashtests/344904-1.svg
new file mode 100644
index 0000000000..a2c8d07647
--- /dev/null
+++ b/layout/svg/crashtests/344904-1.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg" onload="setTimeout(boom, 30);" class="reftest-wait">
+
+<script>
+
+function boom()
+{
+ document.getElementById("m").setAttribute("stroke-miterlimit", 1);
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+
+<marker>
+ <path id="m" />
+</marker>
+
+
+</svg>
diff --git a/layout/svg/crashtests/345418-1.svg b/layout/svg/crashtests/345418-1.svg
new file mode 100644
index 0000000000..2cf8b331fa
--- /dev/null
+++ b/layout/svg/crashtests/345418-1.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<tspan>arg</tspan>
+ </svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/348982-1.xhtml b/layout/svg/crashtests/348982-1.xhtml
new file mode 100644
index 0000000000..ad0340689a
--- /dev/null
+++ b/layout/svg/crashtests/348982-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<style>
+#div, #svg { display: table; }
+#g { display: inline; }
+</style>
+</head>
+
+<body>
+ <div id="div">
+ <svg id="svg" xmlns="http://www.w3.org/2000/svg">
+ <g id="g">
+ <circle cx="6.5cm" cy="2cm" r="100" style="fill: blue;" />
+ </g>
+ </svg>
+ </div>
+</body>
+
+</html>
diff --git a/layout/svg/crashtests/354777-1.xhtml b/layout/svg/crashtests/354777-1.xhtml
new file mode 100644
index 0000000000..e82baf34c9
--- /dev/null
+++ b/layout/svg/crashtests/354777-1.xhtml
@@ -0,0 +1,28 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<script>
+
+var SVG_NS = "http://www.w3.org/2000/svg";
+
+function boom()
+{
+ var svgElem = document.createElementNS(SVG_NS, "svg");
+ var ellipse = document.createElementNS(SVG_NS, "ellipse");
+
+ svgElem.setAttribute("viewBox", "0 0 30 40");
+ document.body.appendChild(svgElem);
+ document.body.appendChild(ellipse);
+ ellipse.appendChild(svgElem);
+ svgElem.removeAttribute("viewBox");
+}
+
+</script>
+</head>
+
+<body onload="boom()">
+
+</body>
+
+</html>
+
diff --git a/layout/svg/crashtests/359516-1.svg b/layout/svg/crashtests/359516-1.svg
new file mode 100644
index 0000000000..c997eb3a85
--- /dev/null
+++ b/layout/svg/crashtests/359516-1.svg
@@ -0,0 +1,36 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ class="reftest-wait"
+ onload="setTimeout(doStuff, 30);">
+
+<html:script style="display: none;" type="text/javascript">
+
+function doStuff()
+{
+ var svg = document.documentElement;
+ var ellipse = document.getElementById("ellipse");
+ var filter = document.getElementById("filter");
+
+ document.addEventListener("DOMNodeRemoved", foopy, false);
+ filter.removeChild(filter.firstChild);
+ document.removeEventListener("DOMNodeRemoved", foopy, false);
+
+ function foopy()
+ {
+ document.removeEventListener("DOMNodeRemoved", foopy, false);
+ svg.appendChild(filter);
+ }
+
+ // Needed for the crash, but not for the assertion.
+ svg.appendChild(ellipse);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+<ellipse id="ellipse" cx="200" cy="150" rx="70" ry="40" style="filter: url(#filter);"/>
+
+<filter id="filter"> </filter>
+
+</svg>
diff --git a/layout/svg/crashtests/361015-1.svg b/layout/svg/crashtests/361015-1.svg
new file mode 100644
index 0000000000..8ac4bc56f2
--- /dev/null
+++ b/layout/svg/crashtests/361015-1.svg
@@ -0,0 +1,33 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ class="reftest-wait"
+ onload="setTimeout(boom, 30)">
+
+<html:script>
+<![CDATA[
+
+function boom()
+{
+ var grad = document.getElementById("grad");
+ var g = document.getElementById("g");
+ grad.appendChild(g);
+ g.removeAttribute("transform");
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</html:script>
+
+
+ <g id="g" transform="translate(500,0)">
+ <text x="25" y="85">Foo</text>
+ </g>
+
+
+ <linearGradient id="grad" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="1" y2="1">
+ <stop stop-color="blue" offset="0.2"/>
+ <stop stop-color="lime" offset="0.4"/>
+ </linearGradient>
+
+
+</svg>
diff --git a/layout/svg/crashtests/361587-1.svg b/layout/svg/crashtests/361587-1.svg
new file mode 100644
index 0000000000..52bce9eda7
--- /dev/null
+++ b/layout/svg/crashtests/361587-1.svg
@@ -0,0 +1,31 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ onload="setTimeout(boom, 30);"
+ class="reftest-wait">
+
+<script style="display: none" type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ var oldGrad = document.getElementById("grad");
+ oldGrad.parentNode.removeChild(oldGrad);
+
+ var newGrad = document.createElementNS("http://www.w3.org/2000/svg", "radialGradient");
+ newGrad.setAttribute("gradientUnits", "userSpaceOnUse");
+ newGrad.setAttribute("id", "grad");
+
+ document.documentElement.appendChild(newGrad);
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</script>
+
+ <radialGradient id="grad" gradientUnits="userSpaceOnUse" cx="240" cy="210" r="220" fx="240" fy="210">
+ <stop stop-color="yellow" offset="0"/>
+ <stop stop-color="green" offset="1"/>
+ </radialGradient>
+ <rect x="20" y="150" width="440" height="80" fill="url(#grad)" stroke-width="40"/>
+
+</svg>
diff --git a/layout/svg/crashtests/363611-1.xhtml b/layout/svg/crashtests/363611-1.xhtml
new file mode 100644
index 0000000000..6bc386bcdf
--- /dev/null
+++ b/layout/svg/crashtests/363611-1.xhtml
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script style="display: none" type="text/javascript">
+
+function boom()
+{
+ var fo = document.createElementNS("http://www.w3.org/2000/svg", 'foreignObject');
+ document.getElementById("innerSVG").appendChild(fo);
+}
+
+</script>
+
+</head>
+
+<body onload="boom();">
+
+<svg xmlns="http://www.w3.org/2000/svg" ><linearGradient><svg id="innerSVG"></svg></linearGradient></svg>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/364688-1.svg b/layout/svg/crashtests/364688-1.svg
new file mode 100644
index 0000000000..045061cd2f
--- /dev/null
+++ b/layout/svg/crashtests/364688-1.svg
@@ -0,0 +1,34 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="setTimeout(boom, 30);"
+ class="reftest-wait">
+
+<script>
+function boom()
+{
+ document.getElementById("sss").removeAttribute('value');
+
+ document.documentElement.removeAttribute("class");
+}
+</script>
+
+
+<foreignObject width="500" height="500" y="300">
+
+<div xmlns="http://www.w3.org/1999/xhtml">
+
+<table border="1">
+ <tr>
+ <td>Foo</td>
+ </tr>
+ <tr>
+ <td><input type="text" value="Baz" id="sss" /></td>
+ </tr>
+</table>
+
+</div>
+
+</foreignObject>
+
+
+</svg>
diff --git a/layout/svg/crashtests/366956-1.svg b/layout/svg/crashtests/366956-1.svg
new file mode 100644
index 0000000000..9836c7ea3a
--- /dev/null
+++ b/layout/svg/crashtests/366956-1.svg
@@ -0,0 +1,61 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" onload="setTimeout(boom1, 50);" class="reftest-wait">
+
+<html:script>
+
+function boom1()
+{
+ document.getElementsByTagName("mi")[0].setAttribute('id', "ffff");
+
+ document.getElementById("fo").appendChild(document.createTextNode(" "));
+
+ setTimeout(boom2, 50);
+}
+
+function boom2()
+{
+ var fodiv = document.getElementById("fodiv");
+ fodiv.parentNode.removeChild(fodiv);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+
+ <g>
+ <foreignObject width="500" height="500" transform="scale(.7,.7)" id="fo" y="300">
+
+<div id="fodiv" xmlns="http://www.w3.org/1999/xhtml">
+
+
+
+<p>0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99</p>
+
+
+
+<div><math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+<mrow>
+ <mi>A</mi>
+</mrow>
+</math></div>
+
+
+<svg xmlns="http://www.w3.org/2000/svg" id="svg" viewbox="0 0 250 250" width="100" height="100">
+ <style type="text/css">
+ circle:hover {fill-opacity:0.9;}
+ </style>
+
+ <g style="fill-opacity:0.7;" transform="scale(.2)">
+ <circle cx="6.5cm" cy="2cm" r="100" style="fill:red; stroke:black; stroke-width:0.1cm" transform="translate(0,50)" />
+ <circle cx="6.5cm" cy="2cm" r="100" style="fill:blue; stroke:black; stroke-width:0.1cm" transform="translate(70,150)" />
+ <circle cx="6.5cm" cy="2cm" r="100" style="fill:green; stroke:black; stroke-width:0.1cm" transform="translate(-70,150)"/>
+ </g>
+</svg>
+
+</div>
+</foreignObject>
+</g>
+
+
+
+</svg>
diff --git a/layout/svg/crashtests/366956-2.svg b/layout/svg/crashtests/366956-2.svg
new file mode 100644
index 0000000000..a2ab21ed55
--- /dev/null
+++ b/layout/svg/crashtests/366956-2.svg
@@ -0,0 +1,61 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" onload="setTimeout(boom1, 30);" class="reftest-wait">
+
+<html:script>
+
+function boom1()
+{
+ document.getElementsByTagName("mi")[0].setAttribute('id', "ffff");
+
+ document.getElementById("fo").appendChild(document.createTextNode(" "));
+
+ boom2();
+}
+
+function boom2()
+{
+ var fodiv = document.getElementById("fodiv");
+ fodiv.parentNode.removeChild(fodiv);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+
+ <g>
+ <foreignObject width="500" height="500" transform="scale(.7,.7)" id="fo" y="300">
+
+<div id="fodiv" xmlns="http://www.w3.org/1999/xhtml">
+
+
+
+<p>0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99</p>
+
+
+
+<div><math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+<mrow>
+ <mi>A</mi>
+</mrow>
+</math></div>
+
+
+<svg xmlns="http://www.w3.org/2000/svg" id="svg" viewbox="0 0 250 250" width="100" height="100">
+ <style type="text/css">
+ circle:hover {fill-opacity:0.9;}
+ </style>
+
+ <g style="fill-opacity:0.7;" transform="scale(.2)">
+ <circle cx="6.5cm" cy="2cm" r="100" style="fill:red; stroke:black; stroke-width:0.1cm" transform="translate(0,50)" />
+ <circle cx="6.5cm" cy="2cm" r="100" style="fill:blue; stroke:black; stroke-width:0.1cm" transform="translate(70,150)" />
+ <circle cx="6.5cm" cy="2cm" r="100" style="fill:green; stroke:black; stroke-width:0.1cm" transform="translate(-70,150)"/>
+ </g>
+</svg>
+
+</div>
+</foreignObject>
+</g>
+
+
+
+</svg>
diff --git a/layout/svg/crashtests/367111-1.svg b/layout/svg/crashtests/367111-1.svg
new file mode 100644
index 0000000000..dcf6a39bf7
--- /dev/null
+++ b/layout/svg/crashtests/367111-1.svg
@@ -0,0 +1,29 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="setTimeout(boom, 30);"
+ class="reftest-wait">
+
+<html:script>
+
+function boom()
+{
+ document.getElementById("text").appendChild(document.getElementById("fo"));
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+<defs>
+ <marker>
+ <text id="text">svg:text</text>
+ </marker>
+</defs>
+
+<foreignObject id="fo">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <p>HTML in a foreignObject</p>
+ </div>
+</foreignObject>
+
+</svg>
diff --git a/layout/svg/crashtests/367368-1.xhtml b/layout/svg/crashtests/367368-1.xhtml
new file mode 100644
index 0000000000..b9bcd3241b
--- /dev/null
+++ b/layout/svg/crashtests/367368-1.xhtml
@@ -0,0 +1,12 @@
+<!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=367368 -->
+<!-- Just checking for crash, nothing more -->
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <circle cx="6.5cm" cy="2cm" r="100" style="fill:red; stroke:black;" transform="translate(0,50)" />
+ </svg>
+
+ </body>
+</html>
diff --git a/layout/svg/crashtests/369233-1.svg b/layout/svg/crashtests/369233-1.svg
new file mode 100644
index 0000000000..22f4aacb37
--- /dev/null
+++ b/layout/svg/crashtests/369233-1.svg
@@ -0,0 +1,33 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="setTimeout(boom, 200);"
+ class="reftest-wait">
+
+<html:script>
+
+function boom()
+{
+ try {
+ document.getElementById("grad2").gradientUnits.baseVal = "y";
+ } catch (e) {
+ }
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+
+
+<radialGradient id="grad2" gradientUnits="userSpaceOnUse" cx="240" cy="210" r="220" fx="240" fy="210">
+ <stop stop-color="black" offset="0"/>
+ <stop stop-color="yellow" offset="0.2"/>
+ <stop stop-color="red" offset="0.4"/>
+ <stop stop-color="blue" offset="0.6"/>
+ <stop stop-color="white" offset="0.8"/>
+ <stop stop-color="green" offset="1"/>
+</radialGradient>
+
+<rect x="20" y="150" width="440" height="80" fill="url(#grad2)" stroke-width="40"/>
+
+</svg>
diff --git a/layout/svg/crashtests/369438-1.svg b/layout/svg/crashtests/369438-1.svg
new file mode 100644
index 0000000000..78bcb6b54d
--- /dev/null
+++ b/layout/svg/crashtests/369438-1.svg
@@ -0,0 +1,24 @@
+<svg width="100%" height="100%" version="1.1"
+xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" onload="setTimeout(boom, 30);" class="reftest-wait"><html:script src="data:text/javascript,"></html:script><html:script>
+
+function boom()
+{
+ var defs = document.getElementById("defs");
+ defs.parentNode.removeChild(defs);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+<defs id="defs">
+<filter id="Gaussian_Blur">
+<feGaussianBlur in="SourceGraphic" stdDeviation="3"/>
+</filter>
+</defs>
+
+<ellipse cx="200" cy="150" rx="70" ry="40"
+style="fill:#ff0000;stroke:#000000;
+stroke-width:2;filter:url(#Gaussian_Blur)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/369438-2.svg b/layout/svg/crashtests/369438-2.svg
new file mode 100644
index 0000000000..92eea9ee0f
--- /dev/null
+++ b/layout/svg/crashtests/369438-2.svg
@@ -0,0 +1,27 @@
+<svg width="100%" height="100%" version="1.1"
+xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" onload="setTimeout(boom, 30);" class="reftest-wait"><html:script>
+
+function boom()
+{
+ var defs = document.getElementById("defs");
+ var gb = document.getElementById("Gaussian_Blur");
+
+ defs.parentNode.removeChild(defs);
+ gb.removeChild(gb.firstChild); // remove a whitespace text node (!)
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+<defs id="defs">
+<filter id="Gaussian_Blur">
+<feGaussianBlur in="SourceGraphic" stdDeviation="3"/>
+</filter>
+</defs>
+
+<ellipse cx="200" cy="150" rx="70" ry="40"
+style="fill:#ff0000;stroke:#000000;
+stroke-width:2;filter:url(#Gaussian_Blur)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/371463-1.xhtml b/layout/svg/crashtests/371463-1.xhtml
new file mode 100644
index 0000000000..461fb27ba3
--- /dev/null
+++ b/layout/svg/crashtests/371463-1.xhtml
@@ -0,0 +1,8 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg">
+<body>
+
+<select><svg:svg><svg:foreignObject/></svg:svg></select>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/371563-1.xhtml b/layout/svg/crashtests/371563-1.xhtml
new file mode 100644
index 0000000000..0ebdc9bfa1
--- /dev/null
+++ b/layout/svg/crashtests/371563-1.xhtml
@@ -0,0 +1,32 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("sdiv").style.overflow = "scroll";
+
+ document.documentElement.removeAttribute("class");
+}
+
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+ <div id="sdiv" style="float: left;">
+
+ <svg xmlns="http://www.w3.org/2000/svg" height="400px" width="400px"
+ y="0.0000000" x="0.0000000" version="1.0" >
+ <defs>
+ <marker id="Arrow"/>
+ </defs>
+ <path style="marker-end:url(#Arrow)"
+ d="M 12.500000,200.00000 L 387.50000,200.00000" />
+ </svg>
+
+ </div>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/375775-1.svg b/layout/svg/crashtests/375775-1.svg
new file mode 100644
index 0000000000..cd17c85a94
--- /dev/null
+++ b/layout/svg/crashtests/375775-1.svg
@@ -0,0 +1,23 @@
+<svg xmlns="http://www.w3.org/2000/svg" onload="setTimeout(boom, 30);" class="reftest-wait">
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("filter").style.display = "none";
+ document.getElementById("path").style.display = "none";
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+<filter id="filter" filterUnits="userSpaceOnUse" x="0" y="0" width="200" height="120" />
+
+<g filter="url(#filter)">
+ <path id="path"
+ fill="black"
+ d="M60,80 C30,80 30,40 60,40 L140,40 C170,40 170,80 140,80 z" />
+</g>
+
+</svg>
diff --git a/layout/svg/crashtests/378716.svg b/layout/svg/crashtests/378716.svg
new file mode 100644
index 0000000000..b6faa00284
--- /dev/null
+++ b/layout/svg/crashtests/378716.svg
@@ -0,0 +1,4 @@
+<svg width="100%" height="100%" x="0" y="0" viewBox="0 0 1 1"
+ xmlns="http://www.w3.org/2000/svg">
+ <text id="text_1" x="0.5" y="0.5" font-size="0.05" fill="green">Okay Text</text>
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/380691-1.svg b/layout/svg/crashtests/380691-1.svg
new file mode 100644
index 0000000000..ed28552633
--- /dev/null
+++ b/layout/svg/crashtests/380691-1.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <mask id="m"/>
+ <foreignObject mask="url(#m)"/>
+</svg>
diff --git a/layout/svg/crashtests/384391-1.xhtml b/layout/svg/crashtests/384391-1.xhtml
new file mode 100644
index 0000000000..12c657a48c
--- /dev/null
+++ b/layout/svg/crashtests/384391-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" >
+<head>
+<script>
+
+function boom()
+{
+ var circle = document.getElementById("circle");
+ document.removeChild(document.documentElement);
+ document.appendChild(circle);
+}
+
+</script>
+</head>
+
+<body onload="boom()">
+
+<svg:circle id="circle" />
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/384499-1.svg b/layout/svg/crashtests/384499-1.svg
new file mode 100644
index 0000000000..f448910008
--- /dev/null
+++ b/layout/svg/crashtests/384499-1.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+
+<html:style>
+ #mathy { display: table}
+</html:style>
+
+<foreignObject width="500" height="500" y="50">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <p>Foo</p>
+ <div>
+ <math xmlns="http://www.w3.org/1998/Math/MathML" id="mathy" display="block">
+ <mrow>
+ <mi>x</mi>
+ </mrow>
+ </math>
+ </div>
+ </div>
+</foreignObject>
+
+</svg>
diff --git a/layout/svg/crashtests/384637-1.svg b/layout/svg/crashtests/384637-1.svg
new file mode 100644
index 0000000000..263a2d556a
--- /dev/null
+++ b/layout/svg/crashtests/384637-1.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" baseProfile="basic" width="100%" height="100%" viewBox="0 0 480 360">
+
+ <mask id="mask1" maskUnits="userSpaceOnUse" x="60" y="50" width="100" height="60">
+ <rect x="60" y="50" width="100" height="60" fill="yellow" mask="url(#mask1)"/>
+ </mask>
+
+ <rect x="60" y="50" width="100" height="60" fill="lime" mask="url(#mask1)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/384728-1.svg b/layout/svg/crashtests/384728-1.svg
new file mode 100644
index 0000000000..ccafc83706
--- /dev/null
+++ b/layout/svg/crashtests/384728-1.svg
@@ -0,0 +1,21 @@
+<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" onload="boom();">
+
+<script>
+
+function boom()
+{
+ document.getElementById("thhh").setAttributeNS("http://www.w3.org/1999/xlink", 'href', '');
+}
+
+</script>
+
+ <defs>
+ <g id="ch" style="counter-reset: c;">
+ <rect x="75" y="0" width="75" height="75" fill="lightgreen" style="counter-increment: c;"/>
+ </g>
+
+ </defs>
+
+ <use id="thhh" x="0" y="0"><use xlink:href="#ch" x="0" y="0"/></use>
+
+</svg>
diff --git a/layout/svg/crashtests/385246-1.svg b/layout/svg/crashtests/385246-1.svg
new file mode 100644
index 0000000000..cddad0c5e4
--- /dev/null
+++ b/layout/svg/crashtests/385246-1.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<foreignObject x="100" y="100" width="-2" height="500">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <p>Foo</p>
+ </div>
+</foreignObject>
+
+</svg>
diff --git a/layout/svg/crashtests/385246-2.svg b/layout/svg/crashtests/385246-2.svg
new file mode 100644
index 0000000000..c392f2fc8a
--- /dev/null
+++ b/layout/svg/crashtests/385246-2.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+<script type="text/javascript" xlink:href="data:text/javascript,"></script>
+
+
+<foreignObject width="-2" height="500" id="fo" x="300">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <p>Hi!!!</p>
+ </div>
+</foreignObject>
+
+
+
+
+</svg>
diff --git a/layout/svg/crashtests/385552-1.svg b/layout/svg/crashtests/385552-1.svg
new file mode 100644
index 0000000000..019e249d77
--- /dev/null
+++ b/layout/svg/crashtests/385552-1.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<script>
+document.createElementNS("http://www.w3.org/2000/svg", "svg").unsuspendRedrawAll();
+</script>
diff --git a/layout/svg/crashtests/385552-2.svg b/layout/svg/crashtests/385552-2.svg
new file mode 100644
index 0000000000..9a93d657fb
--- /dev/null
+++ b/layout/svg/crashtests/385552-2.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<script>
+document.createElementNS("http://www.w3.org/2000/svg", "svg").suspendRedraw(3);
+</script>
diff --git a/layout/svg/crashtests/385840-1.svg b/layout/svg/crashtests/385840-1.svg
new file mode 100644
index 0000000000..cf7ff6949c
--- /dev/null
+++ b/layout/svg/crashtests/385840-1.svg
@@ -0,0 +1,20 @@
+<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" onload="boom();">
+
+<script>
+
+function boom()
+{
+ var SVG_NS = "http://www.w3.org/2000/svg";
+
+ var svgCircle = document.createElementNS(SVG_NS, 'circle');
+ var svgText = document.createElementNS(SVG_NS, 'text');
+ svgText.appendChild(document.createTextNode("foo"));
+ svgCircle.appendChild(svgText);
+
+ document.removeChild(document.documentElement);
+ document.appendChild(svgCircle);
+}
+
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/385852-1.svg b/layout/svg/crashtests/385852-1.svg
new file mode 100644
index 0000000000..17ad99ca8f
--- /dev/null
+++ b/layout/svg/crashtests/385852-1.svg
@@ -0,0 +1,34 @@
+<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" onload="setTimeout(boom, 30)" class="reftest-wait">
+
+<script>
+
+var originalRoot = document.documentElement;
+var svgCircle;
+
+function boom()
+{
+ var SVG_NS = "http://www.w3.org/2000/svg";
+
+ var svgPolyline = document.createElementNS(SVG_NS, 'polyline');
+ svgCircle = document.createElementNS(SVG_NS, 'circle');
+
+ svgCircle.appendChild(svgPolyline);
+
+ document.removeChild(originalRoot);
+ document.appendChild(svgCircle);
+
+ setTimeout(restore, 30);
+}
+
+function restore()
+{
+ // We have to put it the root element back in the document so that reftest.js
+ // sees the event for the removal of class="reftest-wait"!
+ document.removeChild(svgCircle);
+ document.appendChild(originalRoot);
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/386475-1.xhtml b/layout/svg/crashtests/386475-1.xhtml
new file mode 100644
index 0000000000..4d1b9a2808
--- /dev/null
+++ b/layout/svg/crashtests/386475-1.xhtml
@@ -0,0 +1,24 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg">
+<head>
+<script>
+function boom()
+{
+ document.body.style.display = "table-header-group";
+ document.getElementById("svg").setAttribute('height', 1);
+}
+</script>
+</head>
+
+<body onload="boom();">
+
+<svg:svg width="100%" height="100%" id="svg">
+ <svg:g>
+ <svg:foreignObject width="8205em" height="100%">
+ <span>hello</span> <span>world</span>
+ </svg:foreignObject>
+ </svg:g>
+</svg:svg>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/386690-1.svg b/layout/svg/crashtests/386690-1.svg
new file mode 100644
index 0000000000..e206978134
--- /dev/null
+++ b/layout/svg/crashtests/386690-1.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 40">
+ <foreignObject width="-2" height="100" />
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/387290-1.svg b/layout/svg/crashtests/387290-1.svg
new file mode 100644
index 0000000000..4ac8463204
--- /dev/null
+++ b/layout/svg/crashtests/387290-1.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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/. -->
+<!--
+Copyright Georgi Guninski
+-->
+
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+
+<svg version="1.1"
+xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+>
+<defs>
+<filter id="dafilter" filterUnits="userSpaceOnUse"
+x="0" y="0" width="4194305" height="17">
+<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/>
+</filter>
+</defs>
+<g>
+
+<rect fill="red" width="256" height="256" filter="url(#dafilter)" transform="scale(262145,9)" />
+</g>
+
+</svg>
diff --git a/layout/svg/crashtests/402408-1.svg b/layout/svg/crashtests/402408-1.svg
new file mode 100644
index 0000000000..f442b2171e
--- /dev/null
+++ b/layout/svg/crashtests/402408-1.svg
@@ -0,0 +1,32 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ onload="boom();"
+ class="reftest-wait">
+
+<script>
+
+function boom()
+{
+ var grad1 = document.getElementById("grad1");
+ var grad2 = document.getElementById("grad2");
+
+ grad1.appendChild(grad2);
+
+ setTimeout(function() {
+ grad1.removeChild(grad2);
+ document.documentElement.removeAttribute("class");
+ }, 30);
+}
+
+</script>
+
+<linearGradient id="grad1" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="1" y2="0">
+ <stop id="green" stop-color="#00dd00" offset="0"/>
+ <stop id="blue" stop-color="#0000dd" offset="1"/>
+</linearGradient>
+
+<linearGradient id="grad2" xlink:href="#grad1"/>
+
+<rect x="20" y="20" width="440" height="80" fill="url(#grad2)" />
+
+</svg>
diff --git a/layout/svg/crashtests/404677-1.xhtml b/layout/svg/crashtests/404677-1.xhtml
new file mode 100644
index 0000000000..c1df3869b9
--- /dev/null
+++ b/layout/svg/crashtests/404677-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg">
+<head>
+</head>
+<body>
+
+<svg:svg height="-2" width="5" />
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/409565-1.xhtml b/layout/svg/crashtests/409565-1.xhtml
new file mode 100644
index 0000000000..2c427ccc8b
--- /dev/null
+++ b/layout/svg/crashtests/409565-1.xhtml
@@ -0,0 +1,3 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="white-space: pre;"><body style="width: 24px; height: 24px; column-width: 200px;">
+
+ <svg xmlns="http://www.w3.org/2000/svg" style="float: left;"></svg></body></html>
diff --git a/layout/svg/crashtests/420697-1.svg b/layout/svg/crashtests/420697-1.svg
new file mode 100644
index 0000000000..d8b7f38340
--- /dev/null
+++ b/layout/svg/crashtests/420697-1.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <text stroke="black" y="1em"
+ stroke-dashoffset="1%"
+ stroke-dasharray="1px">
+ m
+ </text>
+</svg>
diff --git a/layout/svg/crashtests/420697-2.svg b/layout/svg/crashtests/420697-2.svg
new file mode 100644
index 0000000000..8987693e50
--- /dev/null
+++ b/layout/svg/crashtests/420697-2.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <text stroke="black" y="1em"
+ stroke-dasharray="1%">
+ m
+ </text>
+</svg>
diff --git a/layout/svg/crashtests/429774-1.svg b/layout/svg/crashtests/429774-1.svg
new file mode 100644
index 0000000000..00b726de6a
--- /dev/null
+++ b/layout/svg/crashtests/429774-1.svg
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+
+<svg width="7.5cm" height="5cm" viewBox="0 0 200 120"
+ xmlns="http://www.w3.org/2000/svg">
+
+ <defs>
+ <filter id="MyFilter" filterUnits="userSpaceOnUse" x="0" y="0" width="200" height="120">
+
+ <feOffset in="SourceAlpha" result="offset" dx="4" dy="4" y="76"/>
+
+ <feSpecularLighting in="offset" result="specOut"
+ surfaceScale="5" specularConstant=".75" specularExponent="20">
+ <fePointLight x="-5000" y="-10000" z="20000"/>
+ </feSpecularLighting>
+
+ <feComposite in="SourceAlpha" in2="SourceAlpha" result="litPaint"
+ operator="arithmetic" k1="0" k2="1" k3="1" k4="0"/>
+
+ <feMerge>
+ <feMergeNode in="offset"/>
+ <feMergeNode in="litPaint"/>
+ </feMerge>
+
+ </filter>
+ </defs>
+
+ <g filter="url(#MyFilter)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/441368-1.svg b/layout/svg/crashtests/441368-1.svg
new file mode 100644
index 0000000000..d0fee7478b
--- /dev/null
+++ b/layout/svg/crashtests/441368-1.svg
@@ -0,0 +1,31 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- 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/. -->
+<!--
+Copyright Georgi Guninski
+-->
+
+
+<svg width="100%" height="100%" version="1.1"
+xmlns="http://www.w3.org/2000/svg">
+
+<defs>
+<filter id="MyFilter" filterUnits="userSpaceOnUse"
+x="0" y="0" width="32769" height="32769">
+
+<feGaussianBlur in="SourceAlpha" stdDeviation="2147483648" result="blur"/>
+
+</filter>
+</defs>
+
+<rect x="1" y="1" width="198" height="118" fill="#cccccc" />
+
+<g filter="url(#MyFilter)">
+<text fill="#FFFFFF" stroke="black" font-size="45"
+x="42" y="42">Feck b1ll</text>
+</g>
+
+</svg>
diff --git a/layout/svg/crashtests/453754-1.svg b/layout/svg/crashtests/453754-1.svg
new file mode 100644
index 0000000000..a32d819281
--- /dev/null
+++ b/layout/svg/crashtests/453754-1.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+ <filter id="f" height="-1"/>
+
+ <rect filter="url(#f)" />
+
+</svg>
diff --git a/layout/svg/crashtests/455314-1.xhtml b/layout/svg/crashtests/455314-1.xhtml
new file mode 100644
index 0000000000..01bb33d653
--- /dev/null
+++ b/layout/svg/crashtests/455314-1.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><head>
+<script>
+function doe() {
+document.getElementById('a').appendChild(document.body);
+}
+setTimeout(doe, 100);
+</script>
+</head>
+<body>
+<div style="position: absolute; -moz-appearance: button; filter: url(#b); "></div>
+<pre style="position: absolute;">
+<table id="b"></table>
+</pre>
+</body>
+<div id="a"/>
+</html> \ No newline at end of file
diff --git a/layout/svg/crashtests/458453.html b/layout/svg/crashtests/458453.html
new file mode 100644
index 0000000000..ab72d46dee
--- /dev/null
+++ b/layout/svg/crashtests/458453.html
@@ -0,0 +1,24 @@
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+var i = 0;
+
+function bouncy()
+{
+ var body = document.body;
+ document.documentElement.removeChild(body);
+ document.documentElement.appendChild(body);
+
+ if (++i < 30)
+ setTimeout(bouncy, 1);
+ else
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="bouncy();"><span id="a"></span><span style="filter: url(#a);"><span style="filter: url(#a);">B</span></span></body>
+
+</html>
diff --git a/layout/svg/crashtests/459666-1.html b/layout/svg/crashtests/459666-1.html
new file mode 100644
index 0000000000..69074b6028
--- /dev/null
+++ b/layout/svg/crashtests/459666-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html style="filter: url(#e);">
+<head></head>
+<body onload="document.documentElement.style.counterReset = 'a';">
+<div id="e"></div>
+</body>
+</html>
diff --git a/layout/svg/crashtests/459883.xhtml b/layout/svg/crashtests/459883.xhtml
new file mode 100644
index 0000000000..e125e71d8a
--- /dev/null
+++ b/layout/svg/crashtests/459883.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="filter: url(#r);" class="reftest-wait"><head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("s").setAttribute("style", "display: -moz-box;");
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("load", function() { setTimeout(boom, 0); }, false);
+
+</script>
+</head><body><ms xmlns="http://www.w3.org/1998/Math/MathML" id="s"><maction id="r"/></ms></body></html>
diff --git a/layout/svg/crashtests/461289-1.svg b/layout/svg/crashtests/461289-1.svg
new file mode 100644
index 0000000000..82a57f81b0
--- /dev/null
+++ b/layout/svg/crashtests/461289-1.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+<script type="text/javascript">
+
+function boom()
+{
+ var f = document.getElementById("filter1");
+ f.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "feImage"));
+ f.appendChild(document.getElementById("rect"));
+}
+
+window.addEventListener("load", boom, false);
+
+</script>
+
+<filter id="filter1"/><rect id="rect" filter="url(#filter1)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/464374-1.svg b/layout/svg/crashtests/464374-1.svg
new file mode 100644
index 0000000000..9844e5187f
--- /dev/null
+++ b/layout/svg/crashtests/464374-1.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg" onload="boom();">
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("b").appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math"));
+ document.getElementById("defs").setAttribute("filter", "url(#a)");
+}
+
+</script>
+
+<defs id="defs"><filter id="a"/><g id="b"><rect/></g></defs>
+
+</svg>
diff --git a/layout/svg/crashtests/466585-1.svg b/layout/svg/crashtests/466585-1.svg
new file mode 100644
index 0000000000..22ad862e15
--- /dev/null
+++ b/layout/svg/crashtests/466585-1.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<script type="text/javascript">
+
+window.addEventListener("load", boom, false);
+
+function boom()
+{
+ document.getElementById("rect").setAttribute("filter", "url(#filter)");
+ document.getElementById("defs").setAttribute("fill", "red");
+}
+
+</script>
+
+<defs id="defs"><filter id="filter"/><rect id="rect"/></defs>
+
+</svg>
diff --git a/layout/svg/crashtests/467323-1.svg b/layout/svg/crashtests/467323-1.svg
new file mode 100644
index 0000000000..9d757c349d
--- /dev/null
+++ b/layout/svg/crashtests/467323-1.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+
+<filter id="f1" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
+ <feFlood flood-color="#ff0000" result="flood" x="0" y="0" width="100" height="100"/>
+ <feDisplacementMap style="color-interpolation-filters:sRGB"
+ in="SourceGraphic" in2="flood" scale="100" xChannelSelector="R" yChannelSelector="G"/>
+</filter>
+<g filter="url(#f1)"></g>
+
+</svg>
diff --git a/layout/svg/crashtests/467498-1.svg b/layout/svg/crashtests/467498-1.svg
new file mode 100644
index 0000000000..9839e6c30d
--- /dev/null
+++ b/layout/svg/crashtests/467498-1.svg
@@ -0,0 +1,12 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/licenses/publicdomain/
+-->
+<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <use id="a" width="100" height="100" xlink:href="#b"/>
+ <use id="b" x="100" y="100" width="100" height="100" xlink:href="#a"/>
+ <script>
+ document.getElementById("a").setAttribute("width", "200");
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/470124-1.svg b/layout/svg/crashtests/470124-1.svg
new file mode 100644
index 0000000000..ba3b8aff40
--- /dev/null
+++ b/layout/svg/crashtests/470124-1.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<filter id="f7" filterUnits="userSpaceOnUse" primitiveUnits="objectBoundingBox"><feComposite/></filter>
+
+<g filter="url(#f7)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/472782-1.svg b/layout/svg/crashtests/472782-1.svg
new file mode 100644
index 0000000000..7cfeb11a69
--- /dev/null
+++ b/layout/svg/crashtests/472782-1.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="r">
+<text><textPath xlink:href="#r">S</textPath> </text>
+</svg>
diff --git a/layout/svg/crashtests/474700-1.svg b/layout/svg/crashtests/474700-1.svg
new file mode 100644
index 0000000000..141a1b3903
--- /dev/null
+++ b/layout/svg/crashtests/474700-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"><filter id="f1" height="-2"/><rect width="50" height="100" filter="url(#f1)"/></svg>
diff --git a/layout/svg/crashtests/475181-1.svg b/layout/svg/crashtests/475181-1.svg
new file mode 100644
index 0000000000..ef5a638afb
--- /dev/null
+++ b/layout/svg/crashtests/475181-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><filter id="filter1"><feImage y="4095"/><feTile/></filter><rect width="100%" height="100%" filter="url(#filter1)"/></svg>
diff --git a/layout/svg/crashtests/475193-1.html b/layout/svg/crashtests/475193-1.html
new file mode 100644
index 0000000000..edc08bcee4
--- /dev/null
+++ b/layout/svg/crashtests/475193-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+
+
+<style type="text/css">
+
+.p { marker: url('#c'); }
+
+</style>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("a").setAttribute("class", "p");
+ document.documentElement.offsetHeight;
+ document.getElementById("b").setAttribute("id", "c");
+}
+
+</script>
+</head><body onload="boom();"><div class="p" id="a">C</div><div id="c"></div></body></html> \ No newline at end of file
diff --git a/layout/svg/crashtests/475302-1.svg b/layout/svg/crashtests/475302-1.svg
new file mode 100644
index 0000000000..4fdaa2213c
--- /dev/null
+++ b/layout/svg/crashtests/475302-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<style type="text/css">
+ tref { filter: url(#filter1); }
+</style>
+
+<filter id="filter1"><feFlood/></filter>
+
+<tref><polyline points="350,75 379,161 469,161 397,215 423,301 350,250 277,301 303,215 231,161 321,161"/></tref>
+
+</svg>
diff --git a/layout/svg/crashtests/477935-1.html b/layout/svg/crashtests/477935-1.html
new file mode 100644
index 0000000000..9c2ac5438a
--- /dev/null
+++ b/layout/svg/crashtests/477935-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css"> :root { filter: url('#g'); } </style>
+<style type="text/css" id="ccs"> .cc { content: 'X'; } </style>
+</head>
+<body onload="document.getElementById('ccs').disabled = true;">
+<div id="g"></div>
+<div class="cc">5</div>
+</body>
+</html>
diff --git a/layout/svg/crashtests/478128-1.svg b/layout/svg/crashtests/478128-1.svg
new file mode 100644
index 0000000000..a34552776a
--- /dev/null
+++ b/layout/svg/crashtests/478128-1.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <script type="text/javascript">
+
+ function boom()
+ {
+ document.documentElement.style.columnCount = '15';
+ }
+ window.onload = function() { setTimeout(boom, 20); };
+
+ </script>
+
+ <foreignObject width="50" height="50" filter="url(#f1)"/>
+
+ <filter id="f1"/>
+</svg>
diff --git a/layout/svg/crashtests/478511-1.svg b/layout/svg/crashtests/478511-1.svg
new file mode 100644
index 0000000000..75a4aaa9b2
--- /dev/null
+++ b/layout/svg/crashtests/478511-1.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" version="1.0">
+ <defs>
+ <pattern id="pattern"
+ x="0" y="0" width="200" height="200">
+ <circle fill="lime" r="100" cx="100" cy="100"/>
+ </pattern>
+ </defs>
+ <rect width="200" height="200" fill="url(#pattern)"/>
+</svg>
diff --git a/layout/svg/crashtests/483439-1.svg b/layout/svg/crashtests/483439-1.svg
new file mode 100644
index 0000000000..c9e9ebae1c
--- /dev/null
+++ b/layout/svg/crashtests/483439-1.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <defs>
+ <path id="myTextPath"
+ d="M275,20
+ a1,1 0 0,0 100,0
+ "
+ />
+ </defs>
+
+ <svg y="15">
+ <text x="10" y="100" style="stroke: #000000;">
+ <textPath xlink:href="#myTextPath" >Text along a curved path...</textPath>
+ </text>
+ </svg>
+</svg>
diff --git a/layout/svg/crashtests/492186-1.svg b/layout/svg/crashtests/492186-1.svg
new file mode 100644
index 0000000000..7f4b6650ab
--- /dev/null
+++ b/layout/svg/crashtests/492186-1.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<altGlyphDef/>
+<script xmlns="http://www.w3.org/1999/xhtml">
+document.documentElement.getBBox();
+</script>
+</svg>
diff --git a/layout/svg/crashtests/508247-1.svg b/layout/svg/crashtests/508247-1.svg
new file mode 100644
index 0000000000..c8b36b905f
--- /dev/null
+++ b/layout/svg/crashtests/508247-1.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<definition-src>
+<path id="a"/>
+</definition-src>
+
+<script id="script" xmlns="http://www.w3.org/1999/xhtml">
+setTimeout(function() {document.getElementById('a').getCTM()},10);
+</script>
+</svg>
diff --git a/layout/svg/crashtests/512890-1.svg b/layout/svg/crashtests/512890-1.svg
new file mode 100644
index 0000000000..044f693892
--- /dev/null
+++ b/layout/svg/crashtests/512890-1.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <filter id="f" height="1em"/>
+ <rect width="50" height="50" filter="url(#f)"/>
+</svg>
diff --git a/layout/svg/crashtests/515288-1.html b/layout/svg/crashtests/515288-1.html
new file mode 100644
index 0000000000..d78cbbfbec
--- /dev/null
+++ b/layout/svg/crashtests/515288-1.html
@@ -0,0 +1,5 @@
+<script>
+var svgElem = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+document.createElement("script").appendChild(svgElem);
+svgElem.getScreenCTM();
+</script> \ No newline at end of file
diff --git a/layout/svg/crashtests/522394-1.svg b/layout/svg/crashtests/522394-1.svg
new file mode 100644
index 0000000000..f745c47dd2
--- /dev/null
+++ b/layout/svg/crashtests/522394-1.svg
@@ -0,0 +1,12 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/licenses/publicdomain/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+<defs>
+ <filter id="f1" x="0" y="0" width="-100" height="-100" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
+ <feFlood flood-color="lime" x="0" y="0" width="100%" height="100%"/>
+ </filter>
+</defs>
+<rect x="10" y="10" width="10" height="10" filter="url(#f1)"/>
+</svg>
diff --git a/layout/svg/crashtests/522394-2.svg b/layout/svg/crashtests/522394-2.svg
new file mode 100644
index 0000000000..1b6f1f0892
--- /dev/null
+++ b/layout/svg/crashtests/522394-2.svg
@@ -0,0 +1,12 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/licenses/publicdomain/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+<defs>
+ <filter id="f1" x="0" y="0" width="100000000" height="100000000" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
+ <feFlood flood-color="lime" x="0" y="0" width="100%" height="100%"/>
+ </filter>
+</defs>
+<rect x="10" y="10" width="10" height="10" filter="url(#f1)"/>
+</svg>
diff --git a/layout/svg/crashtests/522394-3.svg b/layout/svg/crashtests/522394-3.svg
new file mode 100644
index 0000000000..cf3483cfad
--- /dev/null
+++ b/layout/svg/crashtests/522394-3.svg
@@ -0,0 +1,12 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/licenses/publicdomain/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+<defs>
+ <filter id="f1" x="0" y="0" width="4563402752" height="4563402752" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
+ <feFlood flood-color="lime" x="0" y="0" width="100%" height="100%"/>
+ </filter>
+</defs>
+<rect x="10" y="10" width="10" height="10" filter="url(#f1)"/>
+</svg>
diff --git a/layout/svg/crashtests/566216-1.svg b/layout/svg/crashtests/566216-1.svg
new file mode 100644
index 0000000000..999aaf4f08
--- /dev/null
+++ b/layout/svg/crashtests/566216-1.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+
+<svg xmlns="http://www.w3.org/2000/svg"><animate id="y"/><script>
+<![CDATA[
+
+function boom()
+{
+ var r = document.createRange();
+ r.setEnd(document.getElementById('y'), 0);
+ r.extractContents();
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+</svg>
+
+
diff --git a/layout/svg/crashtests/587336-1.html b/layout/svg/crashtests/587336-1.html
new file mode 100644
index 0000000000..811f483dd7
--- /dev/null
+++ b/layout/svg/crashtests/587336-1.html
@@ -0,0 +1,9 @@
+<html>
+<head><script>
+function boom()
+{
+ var b = document.getElementById("b");
+ b.setAttributeNS(null, "style", "filter: url(#a);");
+}
+</script></head>
+<body onload="boom();" id="a"><span id="b">B</span></body></html>
diff --git a/layout/svg/crashtests/590291-1.svg b/layout/svg/crashtests/590291-1.svg
new file mode 100644
index 0000000000..db26fac3a8
--- /dev/null
+++ b/layout/svg/crashtests/590291-1.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="0cm" height="0cm">
+
+<text id="a">a</text>
+
+<script><![CDATA[
+var x=document.getElementById('a').getExtentOfChar(0);
+]]></script>
+</svg>
diff --git a/layout/svg/crashtests/601999-1.html b/layout/svg/crashtests/601999-1.html
new file mode 100644
index 0000000000..7e8a3d39de
--- /dev/null
+++ b/layout/svg/crashtests/601999-1.html
@@ -0,0 +1,5 @@
+<html class="reftest-wait">
+ <body onload="document.getElementsByTagName('div')[0].id='b';
+ document.documentElement.removeAttribute('class');"
+ ><div style="overflow-x: scroll; filter: url(#b)">abc</div></body>
+</html>
diff --git a/layout/svg/crashtests/605626-1.svg b/layout/svg/crashtests/605626-1.svg
new file mode 100644
index 0000000000..678b011797
--- /dev/null
+++ b/layout/svg/crashtests/605626-1.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<image width="10" height="10" xlink:href="data:text/plain,g"/>
+</svg>
diff --git a/layout/svg/crashtests/606914.xhtml b/layout/svg/crashtests/606914.xhtml
new file mode 100644
index 0000000000..fc019af573
--- /dev/null
+++ b/layout/svg/crashtests/606914.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="display: table; position: absolute; left: 2305843009213694000pc; bottom: 2452284pc; padding: 9931442138140%; border-bottom-right-radius: 1152921504606847000pc;">X</html>
diff --git a/layout/svg/crashtests/610594-1.html b/layout/svg/crashtests/610594-1.html
new file mode 100644
index 0000000000..ee48e762cc
--- /dev/null
+++ b/layout/svg/crashtests/610594-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<svg><path d="M 0 5 a 2 5 -30 104 -5" marker-mid="url(#q)"></path></svg>
+</body>
+</html>
diff --git a/layout/svg/crashtests/610954-1.html b/layout/svg/crashtests/610954-1.html
new file mode 100644
index 0000000000..f5080df8b8
--- /dev/null
+++ b/layout/svg/crashtests/610954-1.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html><body dir=rtl onload="document.getElementById('g').style.filter = 'url(#filter1)';"><span id="g">&#x200E;---</span></body></html>
diff --git a/layout/svg/crashtests/612662-1.svg b/layout/svg/crashtests/612662-1.svg
new file mode 100644
index 0000000000..73dc98ed19
--- /dev/null
+++ b/layout/svg/crashtests/612662-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" style="background: url(#a); direction: rtl; margin: -32944px;"></svg>
diff --git a/layout/svg/crashtests/612662-2.svg b/layout/svg/crashtests/612662-2.svg
new file mode 100644
index 0000000000..b46841132f
--- /dev/null
+++ b/layout/svg/crashtests/612662-2.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ style="direction: rtl; margin: -32944px;
+ background: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3C%2Fsvg%3E)"></svg>
diff --git a/layout/svg/crashtests/612736-1.svg b/layout/svg/crashtests/612736-1.svg
new file mode 100644
index 0000000000..cb3044efd0
--- /dev/null
+++ b/layout/svg/crashtests/612736-1.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ class="reftest-wait">
+ <path id="path"/>
+ <text>
+ <textPath xlink:href="#path">f</textPath>
+ <textPath xlink:href="#path">f</textPath>
+ </text>
+
+ <script>
+ function boom()
+ {
+ var path = document.getElementById("path");
+ path.parentNode.removeChild(path);
+ document.documentElement.removeAttribute("class");
+ }
+ window.addEventListener("load", boom, false);
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/612736-2.svg b/layout/svg/crashtests/612736-2.svg
new file mode 100644
index 0000000000..30b8245a9f
--- /dev/null
+++ b/layout/svg/crashtests/612736-2.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <path id="path" d="M0 100 h50" stroke="black"/>
+ <text>
+ <textPath xlink:href="#path">abc</textPath>
+ <textPath xlink:href="#path">def</textPath>
+ </text>
+</svg>
diff --git a/layout/svg/crashtests/614367-1.svg b/layout/svg/crashtests/614367-1.svg
new file mode 100644
index 0000000000..3af7b491da
--- /dev/null
+++ b/layout/svg/crashtests/614367-1.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+
+<svg xmlns="http://www.w3.org/2000/svg">
+ <polygon id="p" transform="?" />
+ <script>
+ document.getElementById("p").transform.baseVal.removeItem(0);
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/620034-1.html b/layout/svg/crashtests/620034-1.html
new file mode 100644
index 0000000000..bfffd3ffac
--- /dev/null
+++ b/layout/svg/crashtests/620034-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<script>
+
+function boom()
+{
+ var f = document.createElementNS("http://www.w3.org/2000/svg", "feFuncB");
+ var tvb = f.tableValues.baseVal;
+ f.setAttribute("tableValues", "3 7 5");
+ f.setAttribute("tableValues", "i");
+ tvb.numberOfItems;
+}
+
+boom();
+
+</script>
diff --git a/layout/svg/crashtests/621598-1.svg b/layout/svg/crashtests/621598-1.svg
new file mode 100644
index 0000000000..dd47967d35
--- /dev/null
+++ b/layout/svg/crashtests/621598-1.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <marker id="m1">
+ <rect/>
+ <marker>
+ <line id="z" marker-end="url(#m1)"/>
+ </marker>
+ </marker>
+ <script>
+ function boom()
+ {
+ document.getElementById("z").getBoundingClientRect();
+ }
+ window.addEventListener("load", boom, false);
+ </script>
+
+</svg>
diff --git a/layout/svg/crashtests/648819-1.html b/layout/svg/crashtests/648819-1.html
new file mode 100644
index 0000000000..727ca3e55f
--- /dev/null
+++ b/layout/svg/crashtests/648819-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<script>
+var p = document.createElementNS("http://www.w3.org/2000/svg", "pattern");
+p.setAttribute("patternTransform", "i");
+p.patternTransform.baseVal.clear();
+</script>
diff --git a/layout/svg/crashtests/655025-1.svg b/layout/svg/crashtests/655025-1.svg
new file mode 100644
index 0000000000..4501bb57fa
--- /dev/null
+++ b/layout/svg/crashtests/655025-1.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <text id="a">a</text>
+ <script>
+ document.getElementById("a").firstChild.nodeValue = "";
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/655025-2.svg b/layout/svg/crashtests/655025-2.svg
new file mode 100644
index 0000000000..601006e831
--- /dev/null
+++ b/layout/svg/crashtests/655025-2.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <text id="a">a</text>
+ <script>
+ document.getElementById("a").appendChild(document.createTextNode(""));
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/655025-3.svg b/layout/svg/crashtests/655025-3.svg
new file mode 100644
index 0000000000..43e06b6fc3
--- /dev/null
+++ b/layout/svg/crashtests/655025-3.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <script>
+ var a = document.createElementNS("http://www.w3.org/2000/svg", "text");
+ a.appendChild(document.createTextNode(""));
+ document.documentElement.appendChild(a);
+ a.getNumberOfChars();
+ document.documentElement.removeChild(a);
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/657077-1.svg b/layout/svg/crashtests/657077-1.svg
new file mode 100644
index 0000000000..b0165bd14a
--- /dev/null
+++ b/layout/svg/crashtests/657077-1.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<svg id="a" requiredExtensions="x"/>
+
+<script>
+<![CDATA[
+
+function boom()
+{
+ document.getElementById("a").unsuspendRedrawAll();
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/669025-1.svg b/layout/svg/crashtests/669025-1.svg
new file mode 100644
index 0000000000..eb529da4e0
--- /dev/null
+++ b/layout/svg/crashtests/669025-1.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+ <script type="text/javascript">
+
+ document.createElementNS("http://www.w3.org/2000/svg", "filter").filterResX;
+
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/669025-2.svg b/layout/svg/crashtests/669025-2.svg
new file mode 100644
index 0000000000..ccecebef3c
--- /dev/null
+++ b/layout/svg/crashtests/669025-2.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+ <script type="text/javascript">
+
+ document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur").stdDeviationX;
+
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/682411-1.svg b/layout/svg/crashtests/682411-1.svg
new file mode 100644
index 0000000000..92d0c0a725
--- /dev/null
+++ b/layout/svg/crashtests/682411-1.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<svg xmlns="http://www.w3.org/2000/svg" style="width: 0pt; padding: 100px;">
+ <filter id="s"/>
+ <g filter="url(#s)"><text>z</text></g>
+</svg>
diff --git a/layout/svg/crashtests/692203-1.svg b/layout/svg/crashtests/692203-1.svg
new file mode 100644
index 0000000000..8427b84268
--- /dev/null
+++ b/layout/svg/crashtests/692203-1.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <marker id="marker" markerWidth="0"/>
+ <path d="M0,0 L100,100 200,200" marker-mid="url(#marker)"/>
+</svg>
diff --git a/layout/svg/crashtests/692203-2.svg b/layout/svg/crashtests/692203-2.svg
new file mode 100644
index 0000000000..b59926dbba
--- /dev/null
+++ b/layout/svg/crashtests/692203-2.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <marker id="marker" markerHeight="0"/>
+ <path d="M0,0 L100,100 200,200" marker-mid="url(#marker)"/>
+</svg>
diff --git a/layout/svg/crashtests/693424-1.svg b/layout/svg/crashtests/693424-1.svg
new file mode 100644
index 0000000000..8485f6b617
--- /dev/null
+++ b/layout/svg/crashtests/693424-1.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <marker id="m">
+ <foreignObject/>
+ </marker>
+ <line marker-end="url(#m)"/>
+</svg>
diff --git a/layout/svg/crashtests/709920-1.svg b/layout/svg/crashtests/709920-1.svg
new file mode 100644
index 0000000000..5f25155307
--- /dev/null
+++ b/layout/svg/crashtests/709920-1.svg
@@ -0,0 +1,23 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ class="reftest-wait">
+ <!-- Test to be sure that a zero-sized-in-one-dimension viewBox doesn't
+ make us fail assertions. -->
+ <script>
+ document.addEventListener("MozReftestInvalidate", waitAndFinish, false);
+
+ function waitAndFinish() {
+ // Sadly, MozReftestInvalidate fires sooner than PaintPattern here, so
+ // we need to wait a little bit to give PaintPattern a chance to hit
+ // this bug.
+ setTimeout(finish, 100);
+ }
+
+ function finish() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ <pattern id="test" viewBox="0 0 1 0">
+ <rect/>
+ </pattern>
+ <rect width="200" height="200" fill="url(#test)"/>
+</svg>
diff --git a/layout/svg/crashtests/709920-2.svg b/layout/svg/crashtests/709920-2.svg
new file mode 100644
index 0000000000..58c51111eb
--- /dev/null
+++ b/layout/svg/crashtests/709920-2.svg
@@ -0,0 +1,23 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ class="reftest-wait">
+ <!-- Test to be sure that a zero-sized-in-one-dimension viewBox doesn't
+ make us fail assertions. -->
+ <script>
+ document.addEventListener("MozReftestInvalidate", waitAndFinish, false);
+
+ function waitAndFinish() {
+ // Sadly, MozReftestInvalidate fires sooner than PaintPattern here, so
+ // we need to wait a little bit to give PaintPattern a chance to hit
+ // this bug.
+ setTimeout(finish, 100);
+ }
+
+ function finish() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ <pattern id="test" viewBox="0 0 0 1">
+ <rect/>
+ </pattern>
+ <rect width="200" height="200" fill="url(#test)"/>
+</svg>
diff --git a/layout/svg/crashtests/713413-1.svg b/layout/svg/crashtests/713413-1.svg
new file mode 100644
index 0000000000..7131202335
--- /dev/null
+++ b/layout/svg/crashtests/713413-1.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<marker id="m"></marker>
+
+<script>
+window.addEventListener("load", function() {
+ document.getElementById("m").appendChild(document.createElementNS("http://www.w3.org/2000/svg", "foreignObject"));
+}, false);
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/722003-1.svg b/layout/svg/crashtests/722003-1.svg
new file mode 100644
index 0000000000..58e2d57734
--- /dev/null
+++ b/layout/svg/crashtests/722003-1.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<marker><foreignObject><span id="x" xmlns="http://www.w3.org/1999/xhtml"></span></foreignObject></marker>
+
+<script>
+
+window.addEventListener("load", function() {
+ document.getElementById("x").getClientRects();
+}, false);
+
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/725918-1.svg b/layout/svg/crashtests/725918-1.svg
new file mode 100644
index 0000000000..5ebdf33f69
--- /dev/null
+++ b/layout/svg/crashtests/725918-1.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <text stroke="url(#p)">t</text>
+ <pattern id="p"/>
+</svg>
diff --git a/layout/svg/crashtests/732836-1.svg b/layout/svg/crashtests/732836-1.svg
new file mode 100644
index 0000000000..a9abb46668
--- /dev/null
+++ b/layout/svg/crashtests/732836-1.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
+
+ <symbol id="z">
+ <use xlink:href="data:image/svg+xml,&lt;svg xmlns='http://www.w3.org/2000/svg' id='root' /&gt;#root" />
+ </symbol>
+
+ <use id="a" xlink:href="#z" width="20"/>
+
+ <script>
+ window.addEventListener("load", function() {
+ window.scrollByPages(0);
+ document.getElementById("a").removeAttribute("width");
+ document.elementFromPoint(0, 0);
+ }, false);
+ </script>
+
+</svg>
diff --git a/layout/svg/crashtests/740627-1.svg b/layout/svg/crashtests/740627-1.svg
new file mode 100644
index 0000000000..b74fcd2ddd
--- /dev/null
+++ b/layout/svg/crashtests/740627-1.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <pattern id="test" viewBox="0 0 10 10" height="-65%">
+ <rect/>
+ </pattern>
+ <rect width="100" height="100" fill="url(#test)"/>
+</svg>
diff --git a/layout/svg/crashtests/740627-2.svg b/layout/svg/crashtests/740627-2.svg
new file mode 100644
index 0000000000..6241ddaae5
--- /dev/null
+++ b/layout/svg/crashtests/740627-2.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <pattern id="test" viewBox="0 0 10 10" width="-65%">
+ <rect/>
+ </pattern>
+ <rect width="100" height="100" fill="url(#test)"/>
+</svg>
diff --git a/layout/svg/crashtests/743469.svg b/layout/svg/crashtests/743469.svg
new file mode 100644
index 0000000000..6affc6fab8
--- /dev/null
+++ b/layout/svg/crashtests/743469.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" style="border-width: 51703084143745256mm; border-left-style: dashed; border-top-left-radius: 3%; border-top-style: dashed; border-right-style: solid; border-image-outset: 10;">
+<script>
+document.elementFromPoint(20, 20);
+</script>
+</svg>
diff --git a/layout/svg/crashtests/757704-1.svg b/layout/svg/crashtests/757704-1.svg
new file mode 100644
index 0000000000..b7e610e0e1
--- /dev/null
+++ b/layout/svg/crashtests/757704-1.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+
+<svg xmlns="http://www.w3.org/2000/svg"><script>
+<![CDATA[
+
+function boom()
+{
+ var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
+ document.documentElement.appendChild(rect);
+ document.removeChild(document.documentElement);
+ rect.getScreenCTM();
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script></svg>
diff --git a/layout/svg/crashtests/757718-1.svg b/layout/svg/crashtests/757718-1.svg
new file mode 100644
index 0000000000..fa948c6677
--- /dev/null
+++ b/layout/svg/crashtests/757718-1.svg
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var svgDoc = (new DOMParser).parseFromString("<svg xmlns='http://www.w3.org/2000/svg'></svg>", "image/svg+xml");
+ var svgRoot = svgDoc.documentElement;
+ var rf = svgRoot.requiredFeatures;
+ document.adoptNode(svgRoot);
+ Object.getOwnPropertyNames(rf);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/svg/crashtests/757751-1.svg b/layout/svg/crashtests/757751-1.svg
new file mode 100644
index 0000000000..7ab51d0d19
--- /dev/null
+++ b/layout/svg/crashtests/757751-1.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <svg id="x" viewBox=" 0 0 10 10"/>
+ </defs>
+ <script>
+ window.addEventListener("load", function() { document.getElementById("x").setAttribute("width", "2"); }, false);
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/767056-1.svg b/layout/svg/crashtests/767056-1.svg
new file mode 100644
index 0000000000..b813d784b3
--- /dev/null
+++ b/layout/svg/crashtests/767056-1.svg
@@ -0,0 +1,21 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="100%"
+ class="reftest-wait">
+ <script>
+
+function resize() {
+ // Set the viewBox to the same width as the content area, but slightly
+ // higher. This checks that we don't enter an infinite reflow loop. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=767056#c10
+ var viewBox = "0 0 " + window.innerWidth + " " + (window.innerHeight + 1);
+ document.documentElement.setAttribute("viewBox", viewBox);
+ document.documentElement.removeAttribute("class");
+}
+
+document.addEventListener("MozReftestInvalidate", resize, false);
+setTimeout(resize, 3000); // For non-gecko
+
+ </script>
+ <rect width="100%" height="100%"/>
+</svg>
diff --git a/layout/svg/crashtests/767535-1.xhtml b/layout/svg/crashtests/767535-1.xhtml
new file mode 100644
index 0000000000..2a923fcc6d
--- /dev/null
+++ b/layout/svg/crashtests/767535-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="columns: 2 auto;" class="reftest-wait">
+ <head>
+ <script>
+
+function test() {
+ var r = document.documentElement;
+ document.removeChild(r);
+ document.appendChild(r);
+ document.documentElement.removeAttribute("class");
+}
+
+ </script>
+ </head>
+ <body style="filter:url(#f);" onload="setTimeout(test, 0);">
+ <div>
+ </div>
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <filter id="f"/>
+ </svg>
+ </body>
+</html>
+
diff --git a/layout/svg/crashtests/768087-1.html b/layout/svg/crashtests/768087-1.html
new file mode 100644
index 0000000000..9a7899f9d1
--- /dev/null
+++ b/layout/svg/crashtests/768087-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body onload="setTimeout(function() { document.body.innerHTML = '<span>x<svg viewbox=\'0 0 30 40\' ></svg></span>'; }, 0);"></body>
+</html>
diff --git a/layout/svg/crashtests/768351.svg b/layout/svg/crashtests/768351.svg
new file mode 100644
index 0000000000..50a4b9b9c4
--- /dev/null
+++ b/layout/svg/crashtests/768351.svg
@@ -0,0 +1,2 @@
+<svg xmlns="http://www.w3.org/2000/svg" style="mask: url('data:text/plain,1#f');" />
+
diff --git a/layout/svg/crashtests/772313-1.svg b/layout/svg/crashtests/772313-1.svg
new file mode 100644
index 0000000000..c77af02b6e
--- /dev/null
+++ b/layout/svg/crashtests/772313-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" style="background: url(#d); width: 0.1px;"></svg>
diff --git a/layout/svg/crashtests/778492-1.svg b/layout/svg/crashtests/778492-1.svg
new file mode 100644
index 0000000000..76deda594e
--- /dev/null
+++ b/layout/svg/crashtests/778492-1.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<svg width="1cm" viewBox="0 0 1 1" xmlns="http://www.w3.org/2000/svg">
+<line x2="100" y2="100"/>
+</svg>
diff --git a/layout/svg/crashtests/779971-1.svg b/layout/svg/crashtests/779971-1.svg
new file mode 100644
index 0000000000..d57065a0ba
--- /dev/null
+++ b/layout/svg/crashtests/779971-1.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="r" class="reftest-wait">
+<text id="t"><textPath xlink:href="#r">x</textPath>1</text>
+<script>
+
+window.addEventListener("load", function() {
+ setTimeout(function() {
+ document.getElementById("t").lastChild.data = "2";
+
+ document.documentElement.removeAttribute("class");
+ }, 200);
+}, false);
+
+</script>
+</svg>
diff --git a/layout/svg/crashtests/780764-1.svg b/layout/svg/crashtests/780764-1.svg
new file mode 100644
index 0000000000..6f4eb970bb
--- /dev/null
+++ b/layout/svg/crashtests/780764-1.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait">
+
+<marker id="marker"><path d="M100,0 l100,100 200,200" filter="url(#filter)"/></marker>
+
+<filter id="filter"/>
+
+<path d="M100,0 l100,100 200,200" marker-mid="url(#marker)"/>
+
+<script>
+window.addEventListener("load", function() {
+ setTimeout(function() {
+ document.getElementById("filter").style.fontWeight = "bold";
+ document.documentElement.removeAttribute("class");
+ }, 200);
+}, false);
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/780963-1.html b/layout/svg/crashtests/780963-1.html
new file mode 100644
index 0000000000..8cbeb1a37f
--- /dev/null
+++ b/layout/svg/crashtests/780963-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script>
+
+function tweak() {
+ document.body.offsetTop;
+
+ var feImage = document.getElementsByTagName("feImage")[0];
+ feImage.setAttribute('filter', 'url(#f1)')
+ document.body.offsetTop;
+
+ var child = document.createElementNS('http://www.w3.org/2000/svg', 'g')
+ feImage.appendChild(child);
+}
+
+ </script>
+ </head>
+ <body onload="tweak()">
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <filter filterUnits="userSpaceOnUse" id="f1">
+ <feImage/>
+ </filter>
+ <rect height="100" width="100"/>
+ </svg>
+ </body>
+</html>
diff --git a/layout/svg/crashtests/782141-1.svg b/layout/svg/crashtests/782141-1.svg
new file mode 100644
index 0000000000..6f0af76ff4
--- /dev/null
+++ b/layout/svg/crashtests/782141-1.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
+ onload="go()">
+ <script>
+
+function go() {
+ var f = document.getElementById('f');
+ var fm = document.getElementById('fm');
+ f.appendChild(fm.cloneNode(1));
+}
+
+ </script>
+ <filter id="f">
+ <feMorphology id="fm" radius="2147483500"/>
+ </filter>
+ <rect height="28" width="256" filter="url(#f)" />
+</svg>
diff --git a/layout/svg/crashtests/784061-1.svg b/layout/svg/crashtests/784061-1.svg
new file mode 100644
index 0000000000..6a9623154d
--- /dev/null
+++ b/layout/svg/crashtests/784061-1.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ class="reftest-wait">
+
+<defs><path id="x"/></defs>
+
+<script>
+function boom()
+{
+ document.getElementById("x").style.transform = "translate(0px)";
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("load", boom, false);
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/788831-1.svg b/layout/svg/crashtests/788831-1.svg
new file mode 100644
index 0000000000..b6202a1324
--- /dev/null
+++ b/layout/svg/crashtests/788831-1.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <pattern id="pattern" width="40" height="40"><stop/></pattern>
+ <rect id="rect" width="200" height="100"/>
+ <use xlink:href="#rect" stroke="url(#pattern)" />
+</svg>
diff --git a/layout/svg/crashtests/789390-1.html b/layout/svg/crashtests/789390-1.html
new file mode 100644
index 0000000000..542037f229
--- /dev/null
+++ b/layout/svg/crashtests/789390-1.html
@@ -0,0 +1 @@
+<html style="transition: 1s;"><body onload="document.documentElement.style.stroke = '-moz-objectStroke';"></body></html>
diff --git a/layout/svg/crashtests/790072.svg b/layout/svg/crashtests/790072.svg
new file mode 100644
index 0000000000..b288251a7e
--- /dev/null
+++ b/layout/svg/crashtests/790072.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"><text style="stroke: -moz-objectfill none;">abc</text></svg>
diff --git a/layout/svg/crashtests/791826-1.svg b/layout/svg/crashtests/791826-1.svg
new file mode 100644
index 0000000000..f42261a3ad
--- /dev/null
+++ b/layout/svg/crashtests/791826-1.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" style="position: fixed;">
+<script>
+<![CDATA[
+
+function boom()
+{
+ document.documentElement.setAttribute("preserveAspectRatio", "_");
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+</svg>
diff --git a/layout/svg/crashtests/803562-1.svg b/layout/svg/crashtests/803562-1.svg
new file mode 100644
index 0000000000..e6fc91127e
--- /dev/null
+++ b/layout/svg/crashtests/803562-1.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<foreignObject width="500" height="500" style="-moz-appearance: checkbox;">
+ <option xmlns="http://www.w3.org/1999/xhtml"></option>
+</foreignObject>
+
+<script>
+<![CDATA[
+
+window.addEventListener("load", function() {
+ document.getElementsByTagName("option")[0].appendChild(document.createTextNode("Option 1"));
+}, false);
+
+]]>
+</script>
+
+</svg>
+
diff --git a/layout/svg/crashtests/808318-1.svg b/layout/svg/crashtests/808318-1.svg
new file mode 100644
index 0000000000..48907225cc
--- /dev/null
+++ b/layout/svg/crashtests/808318-1.svg
@@ -0,0 +1,2 @@
+<svg xmlns="http://www.w3.org/2000/svg" style="-moz-transform-style: preserve-3d"></svg>
+
diff --git a/layout/svg/crashtests/813420-1.svg b/layout/svg/crashtests/813420-1.svg
new file mode 100644
index 0000000000..d977c0e982
--- /dev/null
+++ b/layout/svg/crashtests/813420-1.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait">
+ <svg id="i" style="mask: url(&quot;#none&quot;);">
+ <marker id="markerEnd"/><polygon marker-end="url(#markerEnd)" points="250,150 200,150"/>
+ </svg>
+ <script>
+
+window.addEventListener("MozReftestInvalidate", function() {
+ document.getElementById("i").style.mask = "url(#none)";
+ document.documentElement.removeAttribute("class");
+}, false);
+
+ </script>
+</svg>
+
diff --git a/layout/svg/crashtests/841163-1.svg b/layout/svg/crashtests/841163-1.svg
new file mode 100644
index 0000000000..b1bb5198ab
--- /dev/null
+++ b/layout/svg/crashtests/841163-1.svg
@@ -0,0 +1,29 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait">
+
+<filter id="f"/>
+
+<g filter="url(#f)">
+ <text>AB</text>
+</g>
+
+<script>
+
+function forceFrameConstruction()
+{
+ document.documentElement.getBoundingClientRect();
+}
+
+function boom()
+{
+ document.getElementsByTagName("text")[0].firstChild.splitText(1);
+ forceFrameConstruction();
+ document.normalize();
+ forceFrameConstruction();
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("load", boom, false);
+
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/841812-1.svg b/layout/svg/crashtests/841812-1.svg
new file mode 100644
index 0000000000..e5bcaa66ee
--- /dev/null
+++ b/layout/svg/crashtests/841812-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <text>
+ <circle>
+ <textPath id="t" xlink:href="data:text/html,1" />
+ </circle>
+ </text>
+
+ <script>
+ window.addEventListener("load", function() { document.getElementById("t").removeAttribute('xlink:href'); }, false);
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/842009-1.svg b/layout/svg/crashtests/842009-1.svg
new file mode 100644
index 0000000000..25656ba530
--- /dev/null
+++ b/layout/svg/crashtests/842009-1.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <title id="hello">hello</title>
+ <text x="100" y="100"> <tref xlink:href="#hello"/></text>
+</svg>
diff --git a/layout/svg/crashtests/842630-1.svg b/layout/svg/crashtests/842630-1.svg
new file mode 100644
index 0000000000..8d36998be6
--- /dev/null
+++ b/layout/svg/crashtests/842630-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"><text dy="20 20">A<tspan style="display: none;">B</tspan></text></svg>
diff --git a/layout/svg/crashtests/842909-1.svg b/layout/svg/crashtests/842909-1.svg
new file mode 100644
index 0000000000..9a1bc89eb4
--- /dev/null
+++ b/layout/svg/crashtests/842909-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <text id="t">X</text>
+ </defs>
+
+ <script>
+ window.addEventListener("load", function() {
+ document.getElementById("t").getSubStringLength(0, 0);
+ }, false);
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/843072-1.svg b/layout/svg/crashtests/843072-1.svg
new file mode 100644
index 0000000000..590721f058
--- /dev/null
+++ b/layout/svg/crashtests/843072-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <text id="t"></text>
+ </defs>
+
+ <script>
+ window.addEventListener("load", function() {
+ document.getElementById("t").getExtentOfChar(0);
+ }, false);
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/843917-1.svg b/layout/svg/crashtests/843917-1.svg
new file mode 100644
index 0000000000..55cf7ab186
--- /dev/null
+++ b/layout/svg/crashtests/843917-1.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+ <filter id="f"/>
+
+ <g filter="url(#f)">
+ <text>a&#x1e82f;</text>
+ </g>
+
+ <script>
+
+ window.addEventListener("load", function() {
+ var text = document.getElementsByTagName("text")[0];
+ text.firstChild.data = "d";
+ text.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "g"));
+ }, false);
+
+ </script>
+
+</svg>
diff --git a/layout/svg/crashtests/847139-1.svg b/layout/svg/crashtests/847139-1.svg
new file mode 100644
index 0000000000..81fffa4be8
--- /dev/null
+++ b/layout/svg/crashtests/847139-1.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<marker><text>x</text></marker>
+
+<script>
+
+window.addEventListener("load", function() {
+ document.caretPositionFromPoint(0, 0);
+}, false);
+
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/849688-1.svg b/layout/svg/crashtests/849688-1.svg
new file mode 100644
index 0000000000..142f04c933
--- /dev/null
+++ b/layout/svg/crashtests/849688-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<text></text>
+
+<script>
+window.addEventListener("load", function() {
+ document.getElementsByTagName('text')[0].getStartPositionOfChar(1);
+}, false);
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/849688-2.svg b/layout/svg/crashtests/849688-2.svg
new file mode 100644
index 0000000000..4b71b20c7c
--- /dev/null
+++ b/layout/svg/crashtests/849688-2.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<text>X</text>
+
+<script>
+window.addEventListener("load", function() {
+ document.getElementsByTagName('text')[0].getStartPositionOfChar(2);
+}, false);
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/860378-1.svg b/layout/svg/crashtests/860378-1.svg
new file mode 100644
index 0000000000..f4ec09bc4c
--- /dev/null
+++ b/layout/svg/crashtests/860378-1.svg
@@ -0,0 +1,24 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<filter id="f"/>
+<g filter="url(#f)"><text>ab</text></g>
+
+<script>
+
+function boom()
+{
+ var svgtext = document.getElementsByTagName("text")[0];
+ var text1 = svgtext.firstChild ;
+ var text2 = text1.splitText(1);
+
+ setTimeout(function() {
+ text1.data = "c";
+ svgtext.removeChild(text2);
+ }, 200);
+}
+
+window.addEventListener("load", boom, false);
+
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/868904-1.svg b/layout/svg/crashtests/868904-1.svg
new file mode 100644
index 0000000000..c8d7e9437e
--- /dev/null
+++ b/layout/svg/crashtests/868904-1.svg
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+
+* { animation-name: a; animation-duration: 72ms }
+@keyframes a { 60% { transform: skewx(30deg); } }
+
+</style>
+</head>
+<body>
+
+<svg></svg>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/873806-1.svg b/layout/svg/crashtests/873806-1.svg
new file mode 100644
index 0000000000..e40aff201b
--- /dev/null
+++ b/layout/svg/crashtests/873806-1.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <foreignObject requiredFeatures="fail">
+ <svg>
+ <text>a</text>
+ </svg>
+ </foreignObject>
+ <script>
+ document.querySelector("text").getBBox();
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/876831-1.svg b/layout/svg/crashtests/876831-1.svg
new file mode 100644
index 0000000000..6b6c01f9e7
--- /dev/null
+++ b/layout/svg/crashtests/876831-1.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<script>
+
+function boom()
+{
+ var x = document.getElementById("x").firstChild;
+ x.data = x.data.slice(1);
+ document.caretPositionFromPoint(0, 0);
+}
+
+window.addEventListener("load", boom, false);
+
+</script>
+
+<text><tspan id="x">@&#x062A;</tspan></text>
+
+</svg>
diff --git a/layout/svg/crashtests/877029-1.svg b/layout/svg/crashtests/877029-1.svg
new file mode 100644
index 0000000000..1a7bad0f1b
--- /dev/null
+++ b/layout/svg/crashtests/877029-1.svg
@@ -0,0 +1,10 @@
+<!--
+ Check that we don't crash due to an nsSVGMarkerFrame having a null
+ mMarkedFrame and incorrectly calling GetCanvasTM() on the nsSVGMarkerFrame.
+ -->
+<svg xmlns="http://www.w3.org/2000/svg">
+ <marker><text>a</text></marker>
+ <script>
+ document.querySelector("text").getComputedTextLength();
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/880925-1.svg b/layout/svg/crashtests/880925-1.svg
new file mode 100644
index 0000000000..77efd3c0a5
--- /dev/null
+++ b/layout/svg/crashtests/880925-1.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+
+<svg xmlns="http://www.w3.org/2000/svg">
+<script>
+<![CDATA[
+
+function boom()
+{
+ var svgText = document.createElementNS("http://www.w3.org/2000/svg", "text");
+ document.documentElement.appendChild(svgText);
+ var text1 = document.createTextNode("A");
+ svgText.appendChild(text1);
+ var text2 = document.createTextNode("");
+ svgText.appendChild(text2);
+ document.caretPositionFromPoint(0, 0);
+ setTimeout(function() {
+ text2.data = "B";
+ document.caretPositionFromPoint(0, 0);
+ }, 0);
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+</svg>
diff --git a/layout/svg/crashtests/881031-1.svg b/layout/svg/crashtests/881031-1.svg
new file mode 100644
index 0000000000..0738e1299d
--- /dev/null
+++ b/layout/svg/crashtests/881031-1.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<script>
+
+function boom()
+{
+ var svgText = document.getElementById("t");
+ svgText.firstChild.data = "C";
+ svgText.appendChild(document.createTextNode("D"));
+ document.caretPositionFromPoint(0, 0);
+}
+window.addEventListener("load", boom, false);
+
+</script>
+<text id="t">A</text>
+</svg>
diff --git a/layout/svg/crashtests/885608-1.svg b/layout/svg/crashtests/885608-1.svg
new file mode 100644
index 0000000000..0c96777508
--- /dev/null
+++ b/layout/svg/crashtests/885608-1.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<mask id="m"><text id="t">z</text></mask>
+
+<rect width="600" height="400" mask="url(#m)"/>
+
+<script>
+window.addEventListener("load", function() {
+ document.getElementById("t").firstChild.data = "ab";
+}, false);
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/890782-1.svg b/layout/svg/crashtests/890782-1.svg
new file mode 100644
index 0000000000..686bc73a8f
--- /dev/null
+++ b/layout/svg/crashtests/890782-1.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+ <foreignObject requiredFeatures="foo" id="f">
+ <svg>
+ <text id="t"/>
+ </svg>
+ </foreignObject>
+
+ <script>
+ window.addEventListener("load", function() {
+ document.documentElement.appendChild(document.getElementById("f"))
+ document.getElementById("t").getNumberOfChars();
+ }, false);
+ </script>
+
+</svg>
diff --git a/layout/svg/crashtests/890783-1.svg b/layout/svg/crashtests/890783-1.svg
new file mode 100644
index 0000000000..25f54ba2a3
--- /dev/null
+++ b/layout/svg/crashtests/890783-1.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+ <mask id="m">
+ <text>
+ <tspan id="tspan" />
+ </text>
+ </mask>
+
+ <rect width="600" height="400" mask="url(#m)"/>
+
+ <script>
+
+ window.addEventListener("load", function() {
+ document.getElementById("tspan").style.dominantBaseline = "alphabetic";
+ }, false);
+
+ </script>
+
+</svg>
diff --git a/layout/svg/crashtests/893510-1.svg b/layout/svg/crashtests/893510-1.svg
new file mode 100644
index 0000000000..bb58be0450
--- /dev/null
+++ b/layout/svg/crashtests/893510-1.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <g requiredExtensions="foo">
+ <text>&#x062A;z</text>
+ </g>
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/895311-1.svg b/layout/svg/crashtests/895311-1.svg
new file mode 100644
index 0000000000..7b0c728043
--- /dev/null
+++ b/layout/svg/crashtests/895311-1.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+ <mask id="m">
+ <text>
+ <tspan id="ts" />
+ </text>
+ </mask>
+
+ <rect width="600" height="400" mask="url(#m)"/>
+
+ <script>
+ window.addEventListener("load", function() {
+ document.getElementById("ts").style.overflow = "hidden";
+ }, false);
+ </script>
+
+</svg>
diff --git a/layout/svg/crashtests/897342-1.svg b/layout/svg/crashtests/897342-1.svg
new file mode 100644
index 0000000000..547e919b7d
--- /dev/null
+++ b/layout/svg/crashtests/897342-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"><text textLength="50" lengthAdjust="spacingAndGlyphs">&#x200D;</text></svg>
diff --git a/layout/svg/crashtests/898909-1.svg b/layout/svg/crashtests/898909-1.svg
new file mode 100644
index 0000000000..8a70cd7b8d
--- /dev/null
+++ b/layout/svg/crashtests/898909-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" requiredFeatures="foo">
+
+ <text id="t" />
+
+ <script>
+ window.addEventListener("load", function() {
+ document.getElementById("t").getComputedTextLength();
+ }, false);
+ </script>
+
+</svg>
diff --git a/layout/svg/crashtests/898951-1.svg b/layout/svg/crashtests/898951-1.svg
new file mode 100644
index 0000000000..f42dbf69f2
--- /dev/null
+++ b/layout/svg/crashtests/898951-1.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <text>X<tspan style="display: none;">2</tspan>&#x0301;</text>
+</svg>
diff --git a/layout/svg/crashtests/913990.html b/layout/svg/crashtests/913990.html
new file mode 100644
index 0000000000..21d8ef3cc3
--- /dev/null
+++ b/layout/svg/crashtests/913990.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<body style="filter: url('feed:javascript:5');">
+</body>
+</html>
diff --git a/layout/svg/crashtests/919371-1.xhtml b/layout/svg/crashtests/919371-1.xhtml
new file mode 100644
index 0000000000..b27ba3fa66
--- /dev/null
+++ b/layout/svg/crashtests/919371-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <marker style="position: absolute;" />
+ </svg>
+</html>
diff --git a/layout/svg/crashtests/950324-1.svg b/layout/svg/crashtests/950324-1.svg
new file mode 100644
index 0000000000..a43d84f4d8
--- /dev/null
+++ b/layout/svg/crashtests/950324-1.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <text style="font-family: sans-serif;"> &#x0301;</text>
+</svg>
diff --git a/layout/svg/crashtests/951904-1.html b/layout/svg/crashtests/951904-1.html
new file mode 100644
index 0000000000..7a1d1d4dd4
--- /dev/null
+++ b/layout/svg/crashtests/951904-1.html
@@ -0,0 +1,43 @@
+<body onload="go()">
+<svg>
+ <switch>
+ <text id="a">Bonjour</text>
+ <text id="b">Hello</text>
+ <a><text id="c">Hello</text></a>
+ <g>
+ <mask>
+ <text>Lundi</text>
+ </mask>
+ </g>
+ <switch>
+ <text id="d">Au revoir</text>
+ <g>
+ <mask>
+ <text>Mercredi</text>
+ </mask>
+ </g>
+ <text id="e">Goodbye</text>
+ <a><text id="f">Goodbye</text></a>
+ </switch>
+ </switch>
+</svg>
+<svg>
+ <switch>
+ <mask>
+ <text id="g">Vendredi</text>
+ </mask>
+ <a></a>
+ </switch>
+</svg>
+</body>
+<script>
+function go() {
+ a.getComputedTextLength();
+ b.getComputedTextLength();
+ c.getComputedTextLength();
+ d.getComputedTextLength();
+ e.getComputedTextLength();
+ f.getComputedTextLength();
+ g.getComputedTextLength();
+}
+</script>
diff --git a/layout/svg/crashtests/952270-1.svg b/layout/svg/crashtests/952270-1.svg
new file mode 100644
index 0000000000..69bac47d42
--- /dev/null
+++ b/layout/svg/crashtests/952270-1.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <path id="path" transform="scale(2,1)" />
+
+ <text>
+ <textPath xlink:href="#path">F</textPath>
+ </text>
+
+</svg>
diff --git a/layout/svg/crashtests/963086-1.svg b/layout/svg/crashtests/963086-1.svg
new file mode 100644
index 0000000000..3805b46d75
--- /dev/null
+++ b/layout/svg/crashtests/963086-1.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 64 64">
+ <defs>
+ <filter id="dropShadow">
+ <feGaussianBlur stdDeviation="2" />
+ <feOffset
+ result="offsetBlur"
+ dy="1073741824"/>
+ <feMerge>
+ <feMergeNode
+ in="offsetBlur" />
+ <feMergeNode
+ in="SourceGraphic" />
+ </feMerge>
+ </filter>
+ </defs>
+ <rect height="64" width="64" style="filter:url(#dropShadow)" />
+</svg>
diff --git a/layout/svg/crashtests/974746-1.svg b/layout/svg/crashtests/974746-1.svg
new file mode 100644
index 0000000000..c619c25f79
--- /dev/null
+++ b/layout/svg/crashtests/974746-1.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+ <pattern id="patternRotated" width="1" patternTransform="rotate(45 50 50)">
+ <rect/>
+ </pattern>
+
+ <rect width="100" height="100" fill="url(#patternRotated)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/975773-1.svg b/layout/svg/crashtests/975773-1.svg
new file mode 100644
index 0000000000..dd225eb2ae
--- /dev/null
+++ b/layout/svg/crashtests/975773-1.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+ <filter id="f">
+ <feSpecularLighting style="display: none;"/>
+ <feComposite in="SourceGraphic"/>
+ </filter>
+
+ <path d="M0,0 h100 v100 h-100 z M20,20 v60 h60 v-60 z" filter="url(#f)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/979407-1.svg b/layout/svg/crashtests/979407-1.svg
new file mode 100644
index 0000000000..b615f3bec2
--- /dev/null
+++ b/layout/svg/crashtests/979407-1.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <marker id="marker" viewBox="0 0 10 10" markerHeight="-1px"/>
+ <path d="M0,0 h10" marker-start="url(#marker)"/>
+</svg>
diff --git a/layout/svg/crashtests/979407-2.svg b/layout/svg/crashtests/979407-2.svg
new file mode 100644
index 0000000000..75aee06345
--- /dev/null
+++ b/layout/svg/crashtests/979407-2.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <marker id="marker" viewBox="0 0 10 10" markerWidth="-1px"/>
+ <path d="M0,0 h10" marker-start="url(#marker)"/>
+</svg>
diff --git a/layout/svg/crashtests/993443.svg b/layout/svg/crashtests/993443.svg
new file mode 100644
index 0000000000..30bd18543c
--- /dev/null
+++ b/layout/svg/crashtests/993443.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+<image width="10" height="10" x="17592186044416pt"/>
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/blob-merging-and-retained-display-list.html b/layout/svg/crashtests/blob-merging-and-retained-display-list.html
new file mode 100644
index 0000000000..56ca743dc9
--- /dev/null
+++ b/layout/svg/crashtests/blob-merging-and-retained-display-list.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<html class="reftest-wait">
+<script>
+ var r = 40;
+ var xmlns = "http://www.w3.org/2000/svg";
+ const raf = f => requestAnimationFrame(f);
+ function rect(x, y) {
+ var r = document.createElementNS (xmlns, "rect");
+ r.setAttribute("x", x);
+ r.setAttribute("y", y);
+ r.setAttribute("width", "100");
+ r.setAttribute("height", "100");
+ r.setAttribute("fill", "blue");
+ return r;
+ }
+ function f1() {
+ svg = document.getElementById("cnvs");
+ svg.appendChild(rect(0, 0));
+ svg.appendChild(rect(600, 0));
+ svg.appendChild(rect(600, 400));
+ svg.appendChild(rect(0, 400));
+ let a = rect(110, 110);
+ let b = rect(120, 120);
+ let c = rect(130, 130);
+ let d = rect(140, 140);
+ let a2 = rect(310, 140);
+ let b2 = rect(320, 130);
+ let c2 = rect(330, 120);
+ let d2 = rect(340, 110);
+ raf(() => {
+ svg.appendChild(a);
+ svg.appendChild(b);
+ svg.appendChild(c);
+ svg.appendChild(d);
+ raf(() => {
+ // the display list partial update will end up with these items before x,y,w,z
+ svg.appendChild(d2);
+ svg.appendChild(c2);
+ svg.appendChild(b2);
+ svg.appendChild(a2);
+ raf(() => {
+ // this forces all the items to be ordered and makes the new display list
+ // contain reorded items outside of the invalid area
+ let mix = rect(220, 220);
+ svg.insertBefore(mix, d2);
+ raf(() => { document.documentElement.className = "" });
+ })
+ })
+ })
+ }
+
+ function f() {
+ requestAnimationFrame(f1);
+ }
+
+ onload = f;
+</script>
+
+<body>
+<svg width="700" height="600" id=cnvs>
+
+</svg>
diff --git a/layout/svg/crashtests/conditional-outer-svg-nondirty-reflow-assert.xhtml b/layout/svg/crashtests/conditional-outer-svg-nondirty-reflow-assert.xhtml
new file mode 100644
index 0000000000..b23f064a6c
--- /dev/null
+++ b/layout/svg/crashtests/conditional-outer-svg-nondirty-reflow-assert.xhtml
@@ -0,0 +1,28 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg">
+<head>
+ <title>Test of NS_FRAME_IS_NONDISPLAY / NS_FRAME_IS_DIRTY assertion</title>
+ <!-- reduced from layout/reftests/svg/svg-integration/conditions-outer-svg-01.xhtml
+ and modified to occur without bug 1308876 patches. -->
+ <style>
+ div { position: relative; height: 100px; width: 100px }
+ svg { position: absolute; top: 0; left: 0; height: 100%; width: 100% }
+ </style>
+ <script>
+ function run() {
+ var d = document.getElementById("d");
+ d.offsetHeight;
+ d.style.height = "50px";
+ }
+ </script>
+</head>
+<body onload="run()">
+ <div id="d">
+ <svg:svg systemLanguage="x"></svg:svg>
+ </div>
+</body>
+</html>
diff --git a/layout/svg/crashtests/crashtests.list b/layout/svg/crashtests/crashtests.list
new file mode 100644
index 0000000000..4b898383cf
--- /dev/null
+++ b/layout/svg/crashtests/crashtests.list
@@ -0,0 +1,256 @@
+load 220165-1.svg
+load 267650-1.svg
+load 294022-1.svg
+load 307314-1.svg
+load 308615-1.svg
+load 308917-1.svg
+load 310436-1.svg
+load 310638.svg
+load 313737-1.xml
+load chrome://reftest/content/crashtests/layout/svg/crashtests/314244-1.xhtml
+load 322185-1.svg
+load 322215-1.svg
+load 323704-1.svg
+load 325427-1.svg
+load 326495-1.svg
+load 326974-1.svg
+load 327706-1.svg
+load 327711-1.svg
+load 328137-1.svg
+load 329848-1.svg
+load chrome://reftest/content/crashtests/layout/svg/crashtests/337408-1.xhtml
+load 338301-1.xhtml
+load 338312-1.xhtml
+load 340083-1.svg
+load 340945-1.svg
+load 342923-1.html
+load 343221-1.xhtml
+load 344749-1.svg
+load 344887-1.svg
+load 344892-1.svg
+load 344898-1.svg
+load 344904-1.svg
+load 345418-1.svg
+load 348982-1.xhtml
+load 354777-1.xhtml
+load 359516-1.svg
+load 361015-1.svg
+load 361587-1.svg
+load 363611-1.xhtml
+load 364688-1.svg
+load 366956-1.svg
+load 366956-2.svg
+load 367111-1.svg
+load 367368-1.xhtml
+load 369233-1.svg
+load 369438-1.svg
+load 369438-2.svg
+load 371463-1.xhtml
+load 371563-1.xhtml
+load 375775-1.svg
+load 378716.svg
+load 380691-1.svg
+load 384391-1.xhtml
+load 384499-1.svg
+load 384637-1.svg
+load 384728-1.svg
+load 385246-1.svg
+load 385246-2.svg
+load 385552-1.svg
+load 385552-2.svg
+load 385840-1.svg
+load 385852-1.svg
+load 386475-1.xhtml
+load 386690-1.svg
+load 387290-1.svg
+load 402408-1.svg
+load 404677-1.xhtml
+load 409565-1.xhtml
+load 420697-1.svg
+load 420697-2.svg
+load 429774-1.svg
+load 441368-1.svg
+load 453754-1.svg
+load 455314-1.xhtml
+load 458453.html
+load 459666-1.html
+load 459883.xhtml
+load 461289-1.svg
+load 464374-1.svg
+load 466585-1.svg
+load 467323-1.svg
+load 467498-1.svg
+load 470124-1.svg
+load 472782-1.svg
+load 474700-1.svg
+load 475181-1.svg
+load 475193-1.html
+load 475302-1.svg
+load 477935-1.html
+load 478128-1.svg
+load 478511-1.svg
+load 483439-1.svg
+load 492186-1.svg
+load 508247-1.svg
+load 512890-1.svg
+load 515288-1.html
+load 522394-1.svg
+load 522394-2.svg
+load 522394-3.svg
+load 566216-1.svg
+load 587336-1.html
+load 590291-1.svg
+load 601999-1.html
+load 605626-1.svg
+asserts(2) load 606914.xhtml # bug 606914, bug 718883
+load 610594-1.html
+load 610954-1.html
+load 612662-1.svg
+load 612662-2.svg
+load 612736-1.svg
+load 612736-2.svg
+load 614367-1.svg
+load 620034-1.html
+load 621598-1.svg
+load 648819-1.html
+load 655025-1.svg
+load 655025-2.svg
+load 655025-3.svg
+load 657077-1.svg
+load 669025-1.svg
+load 669025-2.svg
+load 682411-1.svg
+load 692203-1.svg
+load 692203-2.svg
+load 693424-1.svg
+load 709920-1.svg
+load 709920-2.svg
+load 713413-1.svg
+load 722003-1.svg
+load 725918-1.svg
+load 732836-1.svg
+load 740627-1.svg
+load 740627-2.svg
+load 743469.svg
+load 757704-1.svg
+load 757718-1.svg
+load 757751-1.svg
+load 767056-1.svg
+load 767535-1.xhtml
+load 768087-1.html
+load 768351.svg
+load 772313-1.svg
+load 778492-1.svg
+load 779971-1.svg
+load 780764-1.svg
+load 780963-1.html
+load 782141-1.svg
+load 784061-1.svg
+load 788831-1.svg
+load 789390-1.html
+load 790072.svg
+load 791826-1.svg
+load 808318-1.svg
+load 803562-1.svg
+load 813420-1.svg
+load 841163-1.svg
+load 841812-1.svg
+load 842009-1.svg
+load 842630-1.svg
+load 842909-1.svg
+load 843072-1.svg
+load 843917-1.svg
+load 847139-1.svg
+load 849688-1.svg
+load 849688-2.svg
+load 860378-1.svg
+load 868904-1.svg
+load 873806-1.svg
+load 876831-1.svg
+load 877029-1.svg
+load 880925-1.svg
+load 881031-1.svg
+load 885608-1.svg
+load 890782-1.svg
+load 890783-1.svg
+load 893510-1.svg
+load 895311-1.svg
+load 897342-1.svg
+load 898909-1.svg
+load 898951-1.svg
+load 913990.html
+load 919371-1.xhtml
+load 950324-1.svg
+load 951904-1.html
+load 952270-1.svg
+load 963086-1.svg
+load 974746-1.svg
+load 975773-1.svg
+load 979407-1.svg
+load 979407-2.svg
+load 993443.svg
+load 1016145.svg
+load 1028512.svg
+load 1140080-1.svg
+load 1149542-1.svg
+load 1156581-1.svg
+load 1182496-1.html
+load 1209525-1.svg
+load 1223281-1.svg
+load 1234726-1.svg
+load 1322537-1.html
+load 1322537-2.html
+load 1322852.html
+load 1348564.svg
+load 1402109.html
+load 1402124.html
+load 1402486.html
+load 1403656-1.html
+load 1403656-2.html
+load 1403656-3.html
+load 1403656-4.html
+load 1403656-5.html
+load 1404086.html
+load 1421807-1.html
+load 1421807-2.html
+load 1422226.html
+load 1425434-1.html
+load 1443092.html
+load 1454201-1.html
+load 1467552-1.html
+load 1474982.html
+load conditional-outer-svg-nondirty-reflow-assert.xhtml
+load extref-test-1.xhtml
+load blob-merging-and-retained-display-list.html
+load empty-blob-merging.html
+load grouping-empty-bounds.html
+load 1480275.html
+load 1480224.html
+load 1502936.html
+load 1504918.svg
+load perspective-invalidation.html
+load invalid_url.html
+load 1535517-1.svg
+load 1504072.html
+load 1072758.html
+load 1536892.html
+load 1539318-1.svg
+load 1548985-1.html
+load 1548985-2.svg
+load 1555851.html
+load invalidation-of-opacity-0.html
+load 1563779.html
+load 1600855.html
+load 1601824.html
+load 1605223-1.html
+load 1609663.html
+skip-if(Android) load 1671950.html # No print-preview support on android
+load 1678947.html
+load 1693032.html
+load 1696505.html
+load 1758029-1.html
+HTTP load 1755770-1.html
+HTTP load 1755770-2.html
+load 1764936-1.html
+load 1804958.html
+load 1810260.html
diff --git a/layout/svg/crashtests/empty-blob-merging.html b/layout/svg/crashtests/empty-blob-merging.html
new file mode 100644
index 0000000000..4b603c19d9
--- /dev/null
+++ b/layout/svg/crashtests/empty-blob-merging.html
@@ -0,0 +1,48 @@
+<html class="reftest-wait">
+<style>
+@keyframes spinnow {
+ 100% {
+ transform: rotate(360deg) scale(.2, .2);
+ }
+}
+
+rect {
+ transform: rotate(0deg) scale(0.6, 1);
+ transform-origin: center;
+ animation: 5s spinnow infinite linear;
+}
+
+</style>
+<svg width=400 height=400>
+ <!--
+ onwheel is needed so that we get a hit test info display item
+ before the transform on the rect
+ -->
+ <g onwheel="alert(1)">
+ <g id="gr">
+ <circle r=30 fill=yellow cx=300 cy=100 />
+ <circle r=30 fill=yellow cx=10 cy=100 />
+ <circle r=30 fill=yellow cx=300 cy=300 />
+ <circle r=30 fill=yellow cx=10 cy=300 />
+ </g>
+ <rect width=100 height=100 fill=blue x=100 y=100 />
+ <g opacity=0.5>
+ <circle r=30 fill=pink cx=300 cy=100 />
+ <circle r=30 fill=pink cx=10 cy=100 />
+ <circle r=30 fill=pink cx=300 cy=300 />
+ <circle r=30 fill=pink cx=10 cy=300 />
+ </g>
+ </g>
+</svg>
+<script>
+ function blam() {
+ let gr = document.getElementById("gr");
+ gr.remove();
+ document.documentElement.removeAttribute("class");
+ }
+document.addEventListener("MozReftestInvalidate", function() {
+ requestAnimationFrame(function() {
+ blam();
+ });
+});
+</script>
diff --git a/layout/svg/crashtests/extref-test-1-resource.xhtml b/layout/svg/crashtests/extref-test-1-resource.xhtml
new file mode 100644
index 0000000000..cd47ddc2f5
--- /dev/null
+++ b/layout/svg/crashtests/extref-test-1-resource.xhtml
@@ -0,0 +1,24 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/licenses/publicdomain/
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+<body style="margin:0">
+ <embed type="application/x-shockwave-flash" src="data:application/x-shockwave-flash,This is a test"></embed>
+ <iframe src="date:text/plain,aaa"></iframe>
+ <div style="mask: url(#m1); width:500px; height:500px; background:lime;"></div>
+
+ <svg:svg height="0">
+ <svg:mask id="m1" maskUnits="objectBoundingBox" maskContentUnits="objectBoundingBox">
+ <svg:linearGradient id="g" gradientUnits="objectBoundingBox" x2="0" y2="1">
+ <svg:stop stop-color="white" offset="0"/>
+ <svg:stop stop-color="white" stop-opacity="0" offset="1"/>
+ </svg:linearGradient>
+ <svg:circle cx="0.25" cy="0.25" r="0.25" id="circle" fill="white"/>
+ <svg:rect x="0.5" y="0" width="0.5" height="1" fill="url(#g)"/>
+ </svg:mask>
+ </svg:svg>
+</body>
+</html>
diff --git a/layout/svg/crashtests/extref-test-1.xhtml b/layout/svg/crashtests/extref-test-1.xhtml
new file mode 100644
index 0000000000..932b679b1f
--- /dev/null
+++ b/layout/svg/crashtests/extref-test-1.xhtml
@@ -0,0 +1,11 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/licenses/publicdomain/
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+<body style="margin:0">
+ <div style="mask: url(extref-test-1-resource.xhtml#m1); width:500px; height:500px; background:lime;"></div>
+</body>
+</html>
diff --git a/layout/svg/crashtests/grouping-empty-bounds.html b/layout/svg/crashtests/grouping-empty-bounds.html
new file mode 100644
index 0000000000..c9f688d0f0
--- /dev/null
+++ b/layout/svg/crashtests/grouping-empty-bounds.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html lang="en" class='reftest-wait'>
+<meta charset="utf-8">
+<title>This testcase might create a non-empty display list with an empty set of drawing commands / items in the EventRecorder</title>
+
+<style>
+
+body {
+ margin: 0;
+}
+
+.animated-opacity {
+ animation: opacity-animation 1s linear alternate infinite;
+}
+
+@keyframes opacity-animation {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+</style>
+
+<svg style="width: 100px; height: 100px;">
+ <rect class="animated-opacity" x="0" y="0" width="100" height="100"/>
+ <rect x="0" y="0" width="10" height="10" id="toremove"/>
+ <g transform="translate(10 10)"><rect x="120" y="0" width="1" height="1"/></g>
+</svg>
+
+<script>
+
+window.addEventListener("MozReftestInvalidate", () => {
+ var elem = document.getElementById("toremove");
+ elem.parentNode.removeChild(elem);
+ document.documentElement.removeAttribute('class');
+});
+
+</script>
diff --git a/layout/svg/crashtests/invalid_url.html b/layout/svg/crashtests/invalid_url.html
new file mode 100644
index 0000000000..ac8671b1ce
--- /dev/null
+++ b/layout/svg/crashtests/invalid_url.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body>
+ <!--
+ If invalid url isn't correctly handled, this test will crash when
+ gfx.webrender.enabled=true and gfx.webrender.all=true
+ -->
+ <div style="padding: 1px; filter:url('data://not-valid-data');"></div>
+</body>
+</html>
diff --git a/layout/svg/crashtests/invalidation-of-opacity-0.html b/layout/svg/crashtests/invalidation-of-opacity-0.html
new file mode 100644
index 0000000000..50700e3908
--- /dev/null
+++ b/layout/svg/crashtests/invalidation-of-opacity-0.html
@@ -0,0 +1,26 @@
+<html class="reftest-wait">
+ <script>
+ var i = 0;
+ var opac = [0.3, 0.2, 0, 0.3];
+
+ function f() {
+ document.getElementById("rim").setAttribute("opacity", opac[i]);
+ document.getElementById("circ").setAttribute("r", i + 10);
+ i++;
+ if (i > opac.length) {
+ document.documentElement.className = ""
+ } else {
+ requestAnimationFrame(f);
+ }
+ }
+ onload = () => requestAnimationFrame(f);
+</script>
+
+<body>
+ <svg height="1000" width="1000">
+ <circle cx="50" cy="50" r="40" fill="red" />
+ <g id=rim clip-path="url(#myClip)" opacity=0>
+ <circle id="circ" cx="150" cy="150" r="40" fill="red" />
+ </g>
+ <circle cx="250" cy="250" r="40" fill="red" />
+ </svg>
diff --git a/layout/svg/crashtests/masked-3d-transform.html b/layout/svg/crashtests/masked-3d-transform.html
new file mode 100644
index 0000000000..6c70aae7fe
--- /dev/null
+++ b/layout/svg/crashtests/masked-3d-transform.html
@@ -0,0 +1,20 @@
+<style>
+ svg,
+svg * {
+ perspective: 1000px;
+}
+</style>
+<svg style="max-width: 176px;" viewBox="0 0 279 169">
+ <g transform="translate(-2.000000, -2.000000)">
+ <g mask="url(#mask-2)">
+ <g transform="translate(19.000000, 22.000000)">
+ <g fill="#FFFFFF">
+ <path d="M43,2.3 C41,2.3 39.9,3.3 39.9,5.6 L39.9,12.4 C39.9,14.7 41,15.7 43.1,15.7 C45.7,15.7 45.9,14.1 46.1,13 C46.1,12.7 46.4,12.4 46.8,12.4 C47.3,12.4 47.5,12.6 47.5,13.3 C47.5,15.3 45.8,17 42.9,17 C40.4,17 38.4,15.7 38.4,12.3 L38.4,5.5 C38.4,2.1 40.5,0.9 43,0.9 C45.9,0.9 47.5,2.6 47.5,4.5 C47.5,5.2 47.3,5.4 46.8,5.4 C46.3,5.4 46.1,5.2 46.1,4.9 C46.1,4 45.6,2.3 43,2.3 L43,2.3 Z"></path>
+ </g>
+ </g>
+ <ellipse fill="#000000" cx="157.9" cy="162.1" rx="157.9" ry="4.3"></ellipse>
+ </g>
+ </g>
+</svg>
+
+
diff --git a/layout/svg/crashtests/perspective-invalidation.html b/layout/svg/crashtests/perspective-invalidation.html
new file mode 100644
index 0000000000..179836d500
--- /dev/null
+++ b/layout/svg/crashtests/perspective-invalidation.html
@@ -0,0 +1,9 @@
+<span>
+<svg id="a" transform="skewX(0)" opacity="0">
+<text>
+</span>
+<marquee>
+A
+</marquee>
+<svg xml:space="preserve">
+<use style="outline: auto; -webkit-perspective: 1px" xlink:href="#a" mask="url(#x)">
diff --git a/layout/svg/moz.build b/layout/svg/moz.build
new file mode 100644
index 0000000000..2543609cfe
--- /dev/null
+++ b/layout/svg/moz.build
@@ -0,0 +1,96 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "SVG")
+
+if CONFIG["ENABLE_TESTS"]:
+ MOCHITEST_MANIFESTS += [
+ "tests/mochitest.ini",
+ ]
+ MOCHITEST_CHROME_MANIFESTS += [
+ "tests/chrome.ini",
+ ]
+
+EXPORTS.mozilla += [
+ "CSSClipPathInstance.h",
+ "FilterInstance.h",
+ "ISVGDisplayableFrame.h",
+ "ISVGSVGFrame.h",
+ "SVGClipPathFrame.h",
+ "SVGContainerFrame.h",
+ "SVGContextPaint.h",
+ "SVGFilterInstance.h",
+ "SVGForeignObjectFrame.h",
+ "SVGGeometryFrame.h",
+ "SVGGradientFrame.h",
+ "SVGImageContext.h",
+ "SVGImageFrame.h",
+ "SVGIntegrationUtils.h",
+ "SVGMaskFrame.h",
+ "SVGObserverUtils.h",
+ "SVGOuterSVGFrame.h",
+ "SVGPaintServerFrame.h",
+ "SVGTextFrame.h",
+ "SVGUseFrame.h",
+ "SVGUtils.h",
+]
+
+UNIFIED_SOURCES += [
+ "CSSClipPathInstance.cpp",
+ "CSSFilterInstance.cpp",
+ "FilterInstance.cpp",
+ "SVGAFrame.cpp",
+ "SVGClipPathFrame.cpp",
+ "SVGContainerFrame.cpp",
+ "SVGContextPaint.cpp",
+ "SVGFEContainerFrame.cpp",
+ "SVGFEImageFrame.cpp",
+ "SVGFELeafFrame.cpp",
+ "SVGFEUnstyledLeafFrame.cpp",
+ "SVGFilterFrame.cpp",
+ "SVGFilterInstance.cpp",
+ "SVGForeignObjectFrame.cpp",
+ "SVGGeometryFrame.cpp",
+ "SVGGFrame.cpp",
+ "SVGGradientFrame.cpp",
+ "SVGImageContext.cpp",
+ "SVGImageFrame.cpp",
+ "SVGInnerSVGFrame.cpp",
+ "SVGIntegrationUtils.cpp",
+ "SVGMarkerFrame.cpp",
+ "SVGMaskFrame.cpp",
+ "SVGObserverUtils.cpp",
+ "SVGOuterSVGFrame.cpp",
+ "SVGPaintServerFrame.cpp",
+ "SVGPatternFrame.cpp",
+ "SVGStopFrame.cpp",
+ "SVGSwitchFrame.cpp",
+ "SVGSymbolFrame.cpp",
+ "SVGTextFrame.cpp",
+ "SVGUseFrame.cpp",
+ "SVGUtils.cpp",
+ "SVGViewFrame.cpp",
+ "SVGViewportFrame.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [
+ "../../widget",
+ "../base",
+ "../generic",
+ "../painting",
+ "../style",
+ "../xul",
+ "/dom/base",
+ "/dom/svg",
+]
+
+RESOURCE_FILES += [
+ "svg.css",
+]
diff --git a/layout/svg/svg.css b/layout/svg/svg.css
new file mode 100644
index 0000000000..af3a55343b
--- /dev/null
+++ b/layout/svg/svg.css
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+@namespace url(http://www.w3.org/2000/svg);
+@namespace xml url(http://www.w3.org/XML/1998/namespace);
+
+style, script {
+ display: none;
+}
+
+/*
+ * This is only to be overridden by the rule right below.
+ *
+ * NOTE(emilio): NodeCouldBeRendered in SVGUseElement.cpp relies on this.
+ */
+symbol {
+ display: none !important;
+}
+
+/*
+ * From https://svgwg.org/svg2-draft/struct.html#SymbolNotes:
+ *
+ * > The generated instance of a 'symbol' that is the direct referenced element
+ * > of a 'use' element must always have a computed value of inline for the
+ * > display property. In other words, it must be rendered whenever the host
+ * > 'use' element is rendered.
+ *
+ * NOTE(emilio): other browsers instead just replace the `<symbol>` element by
+ * an `<svg>` element while cloning, but they don't implement the SVG2
+ * selector-matching rules that would make that observable via selectors.
+ */
+symbol:-moz-use-shadow-tree-root {
+ display: inline !important;
+}
+
+svg:not(:root), symbol, image, marker, pattern, foreignObject {
+ overflow: hidden;
+}
+
+@media all and (-moz-is-glyph) {
+ :root {
+ fill: context-fill;
+ fill-opacity: context-fill-opacity;
+ stroke: context-stroke;
+ stroke-opacity: context-stroke-opacity;
+ stroke-width: context-value;
+ stroke-dasharray: context-value;
+ stroke-dashoffset: context-value;
+ }
+}
+
+foreignObject {
+ appearance: none ! important;
+ margin: 0 ! important;
+ padding: 0 ! important;
+ border-width: 0 ! important;
+ white-space: normal;
+}
+
+@media all and (-moz-is-resource-document) {
+ foreignObject *|* {
+ appearance: none !important;
+ }
+}
+
+*|*::-moz-svg-foreign-content {
+ display: block !important;
+ /* We need to be an absolute and fixed container */
+ transform: translate(0) !important;
+ text-indent: 0;
+}
+
+/* Set |transform-origin:0 0;| for all SVG elements except outer-<svg>,
+ noting that 'svg' as a child of 'foreignObject' counts as outer-<svg>.
+*/
+*:not(svg),
+*:not(foreignObject) > svg {
+ transform-origin:0 0;
+}
+
+*|*::-moz-svg-text {
+ unicode-bidi: inherit;
+ vector-effect: inherit;
+}
+
+*[xml|space=preserve] {
+ white-space: -moz-pre-space;
+}
+
+*|*::-moz-svg-marker-anon-child {
+ clip-path: inherit;
+ filter: inherit;
+ mask: inherit;
+ opacity: inherit;
+}
+
+/* Make SVG shapes unselectable to avoid triggering AccessibleCaret on tap.
+ <mesh> will be supported in bug 1238882. */
+circle, ellipse, line, mesh, path, polygon, polyline, rect {
+ user-select: none;
+}
diff --git a/layout/svg/tests/chrome.ini b/layout/svg/tests/chrome.ini
new file mode 100644
index 0000000000..cc1b542a66
--- /dev/null
+++ b/layout/svg/tests/chrome.ini
@@ -0,0 +1,8 @@
+[test_disabled_chrome.html]
+support-files =
+ svg_example_test.html
+ svg_example_script.svg
+
+[test_context_properties_allowed_domains.html]
+support-files =
+ file_context_fill_fallback_red.html
diff --git a/layout/svg/tests/file_black_yellow.svg b/layout/svg/tests/file_black_yellow.svg
new file mode 100644
index 0000000000..58c5689838
--- /dev/null
+++ b/layout/svg/tests/file_black_yellow.svg
@@ -0,0 +1,9 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <rect height="100%" width="100%" fill="yellow" />
+ <rect height="50px" width="100px" fill="black" />
+</svg>
diff --git a/layout/svg/tests/file_context_fill_fallback_red.html b/layout/svg/tests/file_context_fill_fallback_red.html
new file mode 100644
index 0000000000..ac8b5b4203
--- /dev/null
+++ b/layout/svg/tests/file_context_fill_fallback_red.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<style>
+ img {
+ -moz-context-properties: fill;
+ fill: green;
+ }
+</style>
+<img style="width: 100px; height: 100px;">
+<script>
+ let domain = location.search.substring(1);
+ let img = document.querySelector("img");
+ img.src = img.alt = `http://${domain}/tests/layout/svg/tests/file_context_fill_fallback_red.svg`;
+</script>
diff --git a/layout/svg/tests/file_context_fill_fallback_red.svg b/layout/svg/tests/file_context_fill_fallback_red.svg
new file mode 100644
index 0000000000..9088555b53
--- /dev/null
+++ b/layout/svg/tests/file_context_fill_fallback_red.svg
@@ -0,0 +1,8 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <rect height="100%" width="100%" fill="context-fill red" />
+</svg>
diff --git a/layout/svg/tests/file_disabled_iframe.html b/layout/svg/tests/file_disabled_iframe.html
new file mode 100644
index 0000000000..55eda75fde
--- /dev/null
+++ b/layout/svg/tests/file_disabled_iframe.html
@@ -0,0 +1,81 @@
+<!doctype html>
+<script>
+ window.is = window.parent.is;
+ window.SimpleTest = window.parent.SimpleTest;
+</script>
+<div id="testnodes"><span>hi</span> there <!-- mon ami --></div>
+<script>
+ let t = document.getElementById('testnodes');
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg:svg"));
+ t.firstChild.textContent = "<foo>";
+ is(t.innerHTML, "<svg:svg>&lt;foo&gt;</svg:svg>");
+
+ // This test crashes if the style tags are not handled correctly
+ t.innerHTML = `<svg version="1.1">
+ <style>
+ circle {
+ fill: currentColor;
+ }
+ </style>
+ <g><circle cx="25.8" cy="9.3" r="1.5"/></g>
+ </svg>
+ `;
+ is(t.firstChild.tagName.toLowerCase(), 'svg');
+
+ // This test crashes if the script tags are not handled correctly
+ t.innerHTML = `<svg version="1.1">
+ <scri` + `pt>
+ throw "badment, should never fire.";
+ </scri` + `pt>
+ <g><circle cx="25.8" cy="9.3" r="1.5"/></g>
+ </svg>`;
+ is(t.firstChild.tagName.toLowerCase(), 'svg');
+
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg"));
+ is(t.firstChild.namespaceURI, "http://www.w3.org/2000/svg");
+ t.firstChild.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "script"));
+ is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/2000/svg");
+ t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+ is(t.innerHTML, '<svg><script>1&amp;2&lt;3&gt;4&nbsp;\u003C/script></svg>');
+
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg"));
+ is(t.firstChild.namespaceURI, "http://www.w3.org/2000/svg");
+ t.firstChild.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "style"));
+ is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/2000/svg");
+ t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+ is(t.innerHTML, '<svg><style>1&amp;2&lt;3&gt;4&nbsp;\u003C/style></svg>');
+
+ //
+ // Tests for Bug 1673237
+ //
+
+ // This test fails if about:blank renders SVGs
+ t.innerHTML = null;
+ var iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "about:blank")
+ t.appendChild(iframe);
+ iframe.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg:svg"));
+ iframe.firstChild.textContent = "<foo>";
+ is(iframe.innerHTML, "<svg:svg>&lt;foo&gt;</svg:svg>");
+
+ // This test fails if about:blank renders SVGs
+ var win = window.open("about:blank");
+ win.document.body.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg:svg"))
+ win.document.body.firstChild.textContent = "<foo>";
+ is(win.document.body.innerHTML, "<svg:svg>&lt;foo&gt;</svg:svg>");
+ win.close();
+
+ // This test fails if about:srcdoc renders SVGs
+ t.innerHTML = null;
+ iframe = document.createElement("iframe");
+ iframe.srcdoc = "<svg:svg></svg:svg>";
+ iframe.onload = function() {
+ iframe.contentDocument.body.firstChild.textContent = "<foo>";
+ is(iframe.contentDocument.body.innerHTML, "<svg:svg>&lt;foo&gt;</svg:svg>");
+ SimpleTest.finish();
+ }
+ t.appendChild(iframe);
+</script>
diff --git a/layout/svg/tests/file_embed_sizing_both.svg b/layout/svg/tests/file_embed_sizing_both.svg
new file mode 100644
index 0000000000..8bc39b7d02
--- /dev/null
+++ b/layout/svg/tests/file_embed_sizing_both.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="200" height="400" viewBox="0 0 1 5"></svg>
diff --git a/layout/svg/tests/file_embed_sizing_none.svg b/layout/svg/tests/file_embed_sizing_none.svg
new file mode 100644
index 0000000000..714efc7ef0
--- /dev/null
+++ b/layout/svg/tests/file_embed_sizing_none.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"></svg>
diff --git a/layout/svg/tests/file_embed_sizing_ratio.svg b/layout/svg/tests/file_embed_sizing_ratio.svg
new file mode 100644
index 0000000000..b590bb7da6
--- /dev/null
+++ b/layout/svg/tests/file_embed_sizing_ratio.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 5"></svg>
diff --git a/layout/svg/tests/file_embed_sizing_size.svg b/layout/svg/tests/file_embed_sizing_size.svg
new file mode 100644
index 0000000000..76f50b5f08
--- /dev/null
+++ b/layout/svg/tests/file_embed_sizing_size.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="200" height="900"></svg>
diff --git a/layout/svg/tests/file_filter_crossorigin.svg b/layout/svg/tests/file_filter_crossorigin.svg
new file mode 100644
index 0000000000..a1b9ad0cab
--- /dev/null
+++ b/layout/svg/tests/file_filter_crossorigin.svg
@@ -0,0 +1,25 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <!-- We include these <use> elements simply to be sure the SVG resource URLs
+ get a chance to block 'onload', if they can be loaded. -->
+ <use xlink:href="http://mochi.test:8888/tests/layout/svg/tests/filters.svg#empty" />
+ <use xlink:href="http://example.org/tests/layout/svg/tests/filters.svg#empty" />
+
+ <!-- giant yellow rect in the background, just so you can visually tell
+ that this SVG file has loaded/rendered. -->
+ <rect height="100%" width="100%" fill="yellow" />
+
+ <!-- For both rects below: if it's black, its filter resolved successfully.
+ If it's yellow, it means we failed to load the resource
+ (e.g. because it was blocked as a cross-origin resource). -->
+ <rect height="50px" width="100px" fill="yellow"
+ filter="url(http://mochi.test:8888/tests/layout/svg/tests/filters.svg#NonWhiteToBlack)"/>
+ <rect y="50px"
+ height="50px" width="100px" fill="yellow"
+ filter="url(http://example.org/tests/layout/svg/tests/filters.svg#NonWhiteToBlack)"/>
+</svg>
diff --git a/layout/svg/tests/file_yellow_black.svg b/layout/svg/tests/file_yellow_black.svg
new file mode 100644
index 0000000000..77c14c9af8
--- /dev/null
+++ b/layout/svg/tests/file_yellow_black.svg
@@ -0,0 +1,9 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <rect height="100%" width="100%" fill="yellow" />
+ <rect y="50px" height="50px" width="100px" fill="black" />
+</svg>
diff --git a/layout/svg/tests/filters.svg b/layout/svg/tests/filters.svg
new file mode 100644
index 0000000000..213df7fc93
--- /dev/null
+++ b/layout/svg/tests/filters.svg
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
+ <defs>
+
+ <!-- so that other documents can svg:use this one and force it to
+ load before onload -->
+ <g id="empty" />
+
+ <!-- Keep all white pixels white, and change any others to black. -->
+ <!-- NOTE: alpha is preserved, so it will not adjust alpha edges -->
+ <filter id="NonWhiteToBlack" x="0%" y="0%" width="100%" height="100%">
+ <feComponentTransfer>
+ <feFuncR type="linear" slope="-1" intercept="1" />
+ <feFuncG type="linear" slope="-1" intercept="1" />
+ <feFuncB type="linear" slope="-1" intercept="1" />
+ </feComponentTransfer>
+ <feColorMatrix type="matrix" values="255 255 255 0 0
+ 255 255 255 0 0
+ 255 255 255 0 0
+ 0 0 0 1 0" />
+ <feComponentTransfer>
+ <feFuncR type="linear" slope="-1" intercept="1" />
+ <feFuncG type="linear" slope="-1" intercept="1" />
+ <feFuncB type="linear" slope="-1" intercept="1" />
+ </feComponentTransfer>
+ </filter>
+ </defs>
+</svg>
diff --git a/layout/svg/tests/mochitest.ini b/layout/svg/tests/mochitest.ini
new file mode 100644
index 0000000000..8b1cc1eadd
--- /dev/null
+++ b/layout/svg/tests/mochitest.ini
@@ -0,0 +1,28 @@
+[DEFAULT]
+support-files =
+ file_disabled_iframe.html
+ file_context_fill_fallback_red.svg
+
+[test_disabled.html]
+[test_embed_sizing.html]
+support-files =
+ file_embed_sizing_none.svg
+ file_embed_sizing_size.svg
+ file_embed_sizing_ratio.svg
+ file_embed_sizing_both.svg
+
+[test_filter_crossorigin.html]
+support-files =
+ filters.svg
+ file_filter_crossorigin.svg
+ file_black_yellow.svg
+ file_yellow_black.svg
+# Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+skip-if =
+ xorigin
+ os == "linux" && bits == 64 #Bug 1642198
+ win10_2004 && !debug # Bug 1642198
+[test_hover_near_text.html]
+[test_multiple_font_size.html]
+[test_use_tree_cycle.html]
+[test_bug1544209.html]
diff --git a/layout/svg/tests/svg_example_script.svg b/layout/svg/tests/svg_example_script.svg
new file mode 100644
index 0000000000..5eab758f92
--- /dev/null
+++ b/layout/svg/tests/svg_example_script.svg
@@ -0,0 +1,7 @@
+<svg version="1.1">
+ <script>
+ document.documentElement.style.backgroundColor = 'rebeccapurple';
+ throw "badment, should never fire.";
+ </script>
+ <g><circle cx="25.8" cy="9.3" r="1.5"/></g>
+</svg>
diff --git a/layout/svg/tests/svg_example_test.html b/layout/svg/tests/svg_example_test.html
new file mode 100644
index 0000000000..45c31c98b4
--- /dev/null
+++ b/layout/svg/tests/svg_example_test.html
@@ -0,0 +1,7 @@
+<svg id="layout" viewBox="0 0 120 120" version="1.1"
+ xmlns="http://www.w3.org/2000/svg">
+ <circle cx="60" cy="60" r="50"/>
+</svg>
+
+<svg id="svgel">
+</svg>
diff --git a/layout/svg/tests/test_bug1544209.html b/layout/svg/tests/test_bug1544209.html
new file mode 100644
index 0000000000..b2226b3ea9
--- /dev/null
+++ b/layout/svg/tests/test_bug1544209.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1544209">Mozilla Bug 1544209</a>
+
+<svg height="800px" width="800px">
+ <text transform="scale(3,3)" id="a" x="20px" y="20px">ABC<tspan id="b">ABC</tspan></text>
+</svg>
+
+<script type="application/javascript">
+ let a = document.getElementById("a"),
+ b = document.getElementById("b");
+
+ let wtext = a.getBoundingClientRect().width,
+ wtspan = b.getBoundingClientRect().width;
+
+ ok(wtext >= wtspan, "<tspan> should not be wider than <text>");
+ isfuzzy((wtext - wtspan) / wtext, 0.5, 0.1, "<tspan> should be approximately half of the <text> width");
+</script>
diff --git a/layout/svg/tests/test_context_properties_allowed_domains.html b/layout/svg/tests/test_context_properties_allowed_domains.html
new file mode 100644
index 0000000000..26f38a8770
--- /dev/null
+++ b/layout/svg/tests/test_context_properties_allowed_domains.html
@@ -0,0 +1,95 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Bug 1699892 - SVG context properties for allowed domains</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+<script>
+ /**
+ * Returns a Promise that resolves when target fires a load event.
+ */
+ function waitForLoad(target) {
+ return new Promise(resolve => {
+ target.addEventListener("load", () => {
+ if (event.target == target) {
+ resolve();
+ }}, { once: true });
+ });
+ }
+
+ function makeContextFillFrame(source) {
+ return `
+ <style>
+ img {
+ -moz-context-properties: fill;
+ fill: green;
+ }
+ </style>
+ <img src="${source}" style="width: 100px; height: 100px;">
+ `;
+ }
+
+ /**
+ * Creates an iframe, loads src in it, and waits for the load event
+ * for the iframe to fire. Then it snapshots the iframe and returns
+ * the snapshot (and removes the iframe from the document, to clean up).
+ *
+ * src can be a URL starting with http, or is otherwise assumed to be
+ * a srcdoc string.
+ */
+ async function loadSrcImageAndSnapshot({ src, srcdoc }) {
+ let frame = document.createElement("iframe");
+ document.body.appendChild(frame);
+
+ if (src) {
+ frame.src = src;
+ } else {
+ frame.srcdoc = srcdoc;
+ }
+
+ await waitForLoad(frame);
+
+ let snapshot = await snapshotWindow(frame, false);
+ document.body.removeChild(frame);
+ return snapshot;
+ }
+
+ add_task(async () => {
+ const ALLOWED_DOMAIN = "example.org";
+ const DISALLOWED_DOMAIN = "example.com";
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["svg.context-properties.content.allowed-domains", ALLOWED_DOMAIN]]
+ });
+
+ // When the context properties are allowed, we expect a green
+ // square. When they are not allowed, we expected a red square.
+
+ let redReference = await loadSrcImageAndSnapshot({
+ srcdoc: `<div style="width: 100px; height: 100px; background: red"></div>`,
+ });
+
+ let greenReference = await loadSrcImageAndSnapshot({
+ srcdoc: `<div style="width: 100px; height: 100px; background: green"></div>`,
+ });
+
+ let allowedSnapshot = await loadSrcImageAndSnapshot({
+ src: `file_context_fill_fallback_red.html?${ALLOWED_DOMAIN}`
+ });
+
+ let disallowedSnapshot = await loadSrcImageAndSnapshot({
+ src: `file_context_fill_fallback_red.html?${DISALLOWED_DOMAIN}`
+ });
+
+ const kNoFuzz = null;
+ info("Reference snapshots should look different from each other");
+ assertSnapshots(redReference, greenReference, false, kNoFuzz, "red", "green");
+
+ info("Allowed domain should be green");
+ assertSnapshots(allowedSnapshot, greenReference, true, kNoFuzz, ALLOWED_DOMAIN, "green");
+
+ info("Disallowed domain should be red");
+ assertSnapshots(disallowedSnapshot, redReference, true, kNoFuzz, DISALLOWED_DOMAIN, "red");
+ });
+</script>
+<body>
+</body>
diff --git a/layout/svg/tests/test_disabled.html b/layout/svg/tests/test_disabled.html
new file mode 100644
index 0000000000..acf52e73a2
--- /dev/null
+++ b/layout/svg/tests/test_disabled.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<!--
+Copied from https://bugzilla.mozilla.org/show_bug.cgi?id=744830
+-->
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=166235">Mozilla Bug 166235</a>
+<iframe></iframe>
+<script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [["svg.disabled", true]]}, function() {
+ document.querySelector('iframe').src = "file_disabled_iframe.html";
+ });
+</script>
diff --git a/layout/svg/tests/test_disabled_chrome.html b/layout/svg/tests/test_disabled_chrome.html
new file mode 100644
index 0000000000..e7564f17f2
--- /dev/null
+++ b/layout/svg/tests/test_disabled_chrome.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=744830
+-->
+<head>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=166235">Mozilla Bug 166235</a>
+<div id="testnodes"><span>hi</span> there <!-- mon ami --></div>
+<pre id="test">
+<script type="application/javascript">
+ add_task(async function() {
+ const initialPrefValue = SpecialPowers.getBoolPref("svg.disabled");
+ SpecialPowers.setBoolPref("svg.disabled", true);
+ const Cu = SpecialPowers.Components.utils;
+ const { ContentTaskUtils } = ChromeUtils.import("resource://testing-common/ContentTaskUtils.jsm");
+ let t = document.getElementById('testnodes');
+
+ let url = 'chrome://mochitests/content/chrome/layout/svg/tests/svg_example_test.html'
+ const chromeIframeEl = document.createElement('iframe');
+ let chromeLoadPromise = ContentTaskUtils.waitForEvent(chromeIframeEl, 'load', false);
+ chromeIframeEl.src = url;
+ t.appendChild(chromeIframeEl);
+
+ await chromeLoadPromise;
+ const chromeBR = chromeIframeEl.contentDocument.body.getBoundingClientRect();
+
+ url = "http://mochi.test:8888/chrome/layout/svg/tests/svg_example_test.html";
+ const iframeEl = document.createElement('iframe');
+ iframeEl.src = url;
+ let loadPromise = ContentTaskUtils.waitForEvent(iframeEl, 'load', false);
+ t.appendChild(iframeEl);
+ await loadPromise;
+
+ const contentBR = iframeEl.contentDocument.body.getBoundingClientRect();
+ ok(chromeBR.height > contentBR.height, "Chrome content height should be bigger than content due to layout");
+
+ url = "http://mochi.test:8888/chrome/layout/svg/tests/svg_example_script.svg";
+ const iframeElScript = document.createElement("iframe");
+ let loadPromiseScript = ContentTaskUtils.waitForEvent(iframeElScript, "load", false);
+ iframeElScript.src = url;
+ t.appendChild(iframeElScript);
+ await loadPromiseScript;
+ ok(!iframeElScript.contentDocument.documentElement.style, "Content should not be styled");
+
+ SpecialPowers.setBoolPref("svg.disabled", initialPrefValue);
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/svg/tests/test_embed_sizing.html b/layout/svg/tests/test_embed_sizing.html
new file mode 100644
index 0000000000..9afbc7ca7d
--- /dev/null
+++ b/layout/svg/tests/test_embed_sizing.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+
+<p>Test intrinsic sizing of embed elements referencing SVG documents, both same
+origin and cross origin.</p>
+
+<div id="container" style="width: 500px;"></div>
+
+<script>
+const TESTS = [
+ { outer: "none", inner: "none", expected: "300x150" },
+ { outer: "none", inner: "size", expected: "200x900" },
+ { outer: "none", inner: "ratio", expected: "500x2500" },
+ { outer: "none", inner: "both", expected: "200x400" },
+ { outer: "size", inner: "none", expected: "100x150" },
+ { outer: "size", inner: "size", expected: "100x450" },
+ { outer: "size", inner: "ratio", expected: "100x500" },
+ { outer: "size", inner: "both", expected: "100x200" },
+ { outer: "ratio", inner: "none", expected: "500x1500" },
+ { outer: "ratio", inner: "size", expected: "200x900" },
+ { outer: "ratio", inner: "ratio", expected: "500x1500" },
+ { outer: "ratio", inner: "both", expected: "200x400" },
+ { outer: "both", inner: "none", expected: "100x300" },
+ { outer: "both", inner: "size", expected: "100x300" },
+ { outer: "both", inner: "ratio", expected: "100x300" },
+ { outer: "both", inner: "both", expected: "100x300" },
+];
+
+add_task(async function() {
+ for (let test of TESTS) {
+ for (let crossorigin of [false, true]) {
+ let host = crossorigin ? "http://example.org" : "http://mochi.test:8888";
+ let e = document.createElement("embed");
+
+ switch (test.outer) {
+ case "none":
+ break;
+ case "size":
+ e.style.width = "100px";
+ break;
+ case "ratio":
+ e.style.aspectRatio = "1 / 3";
+ break;
+ case "both":
+ e.style.width = "100px";
+ e.style.aspectRatio = "1 / 3";
+ break;
+ default:
+ throw new Error("unexpected subtest");
+ }
+
+ await new Promise(function(resolve) {
+ e.src = host + location.pathname.replace("test_embed_sizing.html", `file_embed_sizing_${test.inner}.svg`);
+ e.onload = resolve;
+ container.append(e);
+ });
+
+ let desc = `Subtest (${test.outer}/${test.inner}/${crossorigin ? 'cross' : 'same'} origin)`;
+ is(`${e.offsetWidth}x${e.offsetHeight}`, test.expected, desc);
+ e.remove();
+ }
+ }
+});
+</script>
diff --git a/layout/svg/tests/test_filter_crossorigin.html b/layout/svg/tests/test_filter_crossorigin.html
new file mode 100644
index 0000000000..e31c13bee1
--- /dev/null
+++ b/layout/svg/tests/test_filter_crossorigin.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=695385
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 695385</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=695385">Mozilla Bug 695385</a>
+<br>
+<!-- These iframes' renderings are expected to match: -->
+<iframe src="http://mochi.test:8888/tests/layout/svg/tests/file_filter_crossorigin.svg"></iframe>
+<iframe src="file_black_yellow.svg"></iframe>
+<br>
+<!-- These iframes' renderings are expected to match: -->
+<iframe src="http://example.org/tests/layout/svg/tests/file_filter_crossorigin.svg"></iframe>
+<iframe src="file_yellow_black.svg"></iframe>
+
+<pre id="test">
+<script type="application/javascript">
+
+function promiseExecuteSoon() {
+ return new Promise(SimpleTest.executeSoon);
+}
+
+// Main Function
+async function run() {
+ SimpleTest.waitForExplicitFinish();
+
+ // XXXdholbert Wait a few ticks, to give the iframes a little more
+ // opportunity to load their external resources before we call
+ // snapshotWindow. This is an attempt at a workaround/diagnostic for
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1706542
+ for (let i = 0; i < 60; i++) {
+ await promiseExecuteSoon();
+ }
+
+ let snapshots = new Array(4);
+ for (let i = 0; i < snapshots.length; i++) {
+ snapshots[i] = await snapshotWindow(frames[i], false);
+ }
+
+ // Compare mochi.test iframe against its reference:
+ assertSnapshots(snapshots[0], snapshots[1], true, null,
+ "Testcase loaded from mochi.test", "Reference: black/yellow");
+
+ // Compare example.org iframe against its reference:
+ assertSnapshots(snapshots[2], snapshots[3], true, null,
+ "Testcase loaded from example.org", "Reference: yellow/black");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/svg/tests/test_hover_near_text.html b/layout/svg/tests/test_hover_near_text.html
new file mode 100644
index 0000000000..2bf808e61a
--- /dev/null
+++ b/layout/svg/tests/test_hover_near_text.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8"/>
+ <title>Test mouse hover near text element inside svg element with viewBox attribute</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ </head>
+ <body style="margin:0">
+ <div>
+ <svg viewBox="-1 -1 2 2" width="300" height="300">
+ <text style="font-size:0.1px" onmouseover="this.setAttribute('fill', 'red')">Hi</text>
+ </svg>
+ </div>
+ <p>
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1519144">Mozilla Bug 1519144</a>
+ </p>
+ <script type="application/javascript">
+ let utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent('mousemove', 155, 125, 0, 0, 0); //hover above the text
+ utils.sendMouseEvent('mousemove', 125, 155, 0, 0, 0); //hover to the left of the text
+ requestIdleCallback(() => {
+ ok(!document.getElementsByTagName('text')[0].hasAttribute('fill'),
+ 'Text element should not receive an event');
+ SimpleTest.finish();
+ });
+ SimpleTest.waitForExplicitFinish()
+ </script>
+ </body>
+</html>
diff --git a/layout/svg/tests/test_multiple_font_size.html b/layout/svg/tests/test_multiple_font_size.html
new file mode 100644
index 0000000000..aca32eac03
--- /dev/null
+++ b/layout/svg/tests/test_multiple_font_size.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1370646">Mozilla Bug 1370646</a>
+
+<svg xmlns="http://www.w3.org/2000/svg" width="440" height="100" viewBox="0 0 440 100">
+ <text>
+ <tspan id="a" style="font-size:100px">3</tspan>
+ </text>
+ <text>
+ <tspan id="b" style="font-size:100px">3</tspan>
+ <tspan style="font-size:0.1px">0</tspan>
+ </text>
+</svg>
+
+<script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ let alen = document.getElementById("a").getComputedTextLength(),
+ blen = document.getElementById("b").getComputedTextLength();
+
+ SimpleTest.isfuzzy(alen, blen, 5, "lengths should be close");
+
+ SimpleTest.finish();
+</script>
diff --git a/layout/svg/tests/test_use_tree_cycle.html b/layout/svg/tests/test_use_tree_cycle.html
new file mode 100644
index 0000000000..e820f0f2a2
--- /dev/null
+++ b/layout/svg/tests/test_use_tree_cycle.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<title>Test for bug 1531333</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<style>
+ symbol { display: block }
+</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="10" height="10">
+ <symbol id="svg-sprite" viewBox="0 0 133 230866">
+ <title>svg-sprite</title>
+ <symbol id="svg-sprite" viewBox="0 0 133 230866">
+ <title>svg-sprite</title>
+ <use xlink:href="#svg-sprite" width="500" height="500" />
+ </symbol>
+ <use xlink:href="#svg-sprite" y="1601" width="133" height="228958" />
+ </symbol>
+ <use xlink:href="#svg-sprite" y="1601" width="133" height="230866" />
+</svg>
+<script>
+function countUseElements(subtreeRoot) {
+ if (!subtreeRoot)
+ return 0;
+
+ let i = 0;
+ for (const use of subtreeRoot.querySelectorAll("use"))
+ i += 1 + countUseElements(SpecialPowers.wrap(use).openOrClosedShadowRoot);
+ return i;
+}
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+ document.body.offsetTop;
+ // The three in the document, plus the two created from the element that's
+ // not in the <symbol> subtree.
+ is(countUseElements(document), 5, "Shouldn't create more than 5 use elements");
+ SimpleTest.finish();
+}
+</script>