Almost two decades since its introduction the legacy SVG engine shows its age. Today’s Web world is dominated by HTML/CSS. They are actively developed, have strong communities and vendor support. SVG should be a first-class citizen in the HTML/CSS world – no one should need to use animated CSS masks combined with ‘border-radius’ to simulate animated circles, just because animating SVGs itself is too expensive.
Why is animating SVGs so expensive at present in WebKit? Consider a standalone SVG document with a single rectangle, that’s animated:
<svg xmlns="http://www.w3.org/2000/svg" width="400px" height="200px">
<style type="text/css">
rect {
transform: rotate(0deg);
transform-box: fill-box;
transform-origin: 50% 50%;
animation: rotation 5s infinite;
}
@keyframes rotation {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
<rect x="50" y="50" width="50" height="50" fill="green"/>
</svg>
Output:
For each animation frame the CSS style for the <rect> needs to be invalidated and recalculated. That’s unavoidable since the CSS animation modified CSS style rules. Furthermore a relayout has to be performed, due to changes in the local transformation matrix of the rectangle. Finally the whole scene needs to be repainted. The same example with a HTML <div> element would not lead to any relayout, since the RenderLayer associated with the <div> element will offload this work to the compositor – the rectangle will only be painted once, untransformed, and then transformed by the compositor. That’s the fastest solution you can imagine, that does not involve complex operations in WebCore, such as relayout.
The obvious fix for the problem is to make SVG aware of the Layer Tree. If that is accomplished, hardware acceleration comes to SVG “for free”, as side-effect of the participation in the Layer Tree. But before we can discuss design changes, let’s revisit the current design of the Render Tree in WebCore.
The graph shows the class hierarchy of the Render Tree. RenderObject is the base class for all nodes in the Render Tree. RenderBoxModelObject is the common base class for all HTML/CSS renderers. It inherits from RenderLayerModelObject, potentially allowing all HTML/CSS renderers to create layers. For the SVG part of the Render Tree there is no common base class shared by all the SVG renderers, for historical reasons.
As described in an earlier section a SVG document fragment always starts with a <svg> element, the so-called outermost <svg> element, which owns the RenderSVGRoot renderer. All other SVG renderers are always descendants of the containing RenderSVGRoot renderer. From CSS point of view, the outermost <svg> element is a replaced element, whose content is defined outside the scope of CSS (potentially using different layout rules than CSS).
The CSS style rules from the current document do not propagate to the content of the replaced element. However it is possible to alter the position of replaced elements using CSS, for example by applying ‘display’ / ‘position’ properties on an outermost <svg> element embedded in an HTML document. This technique allows to lay out SVG documents ‘inline’ or as ‘blocks’ embedded within a HTML document. Therefore RenderSVGRoot inherits from RenderReplaced in WebCore.
Since all other SVG renderers don’t follow the CSS box model object, one might expect that RenderSVGModelObject is the base class for the remaining SVG renderers. However RenderSVG(Text|TSpan|TextPath) and RenderSVGForeignObject don’t inherit from RenderSVGModelObject, but instead from either RenderSVGBlock or RenderSVGInline. These renderers inherit from RenderBlock / RenderInline, which both adhere to the CSS box model. Why is that?
The SVG text layout rules do not refer to ‘inline’ or ‘block’" level elements, or any related CSS box model terminology. Before SVG text support was first added to WebCore, WebCore already contained an advanced text rendering implementation, formulated in the language of CSS: inline boxes / block-level elements / etc. Furthermore a complete implementation of the Unicode BiDi algorithm was present - which is a requirement for confirming SVG 1.1 viewers. Following the hint in SVG 1.1 to treat the SVG <text> element as block-level element and all potential children (such as <tspan> / <textPath>) as inline-level elements, one can re-use the HTML/CSS text layout code to manage the painting/hit-testing/etc. of the SVG <text> subtree. The SVG text layout rules (chunk creation, x/y/dx/dy lists, etc.) can be implemented on top of that, which was done in the early days of SVG integration in WebKit. Therefore RenderSVGText inherits from RenderSVGBlock and RenderSVG(TSpan|TextPath) from RenderSVGInline.
Besides the SVG text renderers, RenderSVGForeignObject also does not inherit from RenderSVGModelObject. SVG <foreignObject> is commonly used to embed e.g. HTML/MathML document fragments into a SVG document. From CSS point of view it’s a natural choice to treat the <foreignObject> as block-level element and use the RenderBlockFlow layout logic to layout the descendants of the RenderSVGForeignObject, assuming they follow CSS layout rules. This requirement is fulfilled for both HTML and MathML document fragments. Therefore RenderSVGForeignObject inherits from RenderSVGBlock.
Two SVG renderers have special roles in the SVG Render Tree: RenderSVGRoot and RenderSVGForeignObject, that we’ll discuss in the following two sections.
Consider the following SVG document fragment:
<style>
.div-container {
border: 10px solid black;
width: 300px;
height: 300px;
left: 500px;
position: relative;
}
.svg-border {
border: 2px dotted red;
width: 400px;
height: 200px;
margin-top: 60px;
}
</style>
<div class="div-container">
<svg class="svg-border" viewBox="0 0 10 10" preserveAspectRatio="none">
<circle cx="5" cy="5" r="4" fill="green"/>
</svg>
</div>
Output:
The outer <div> element generates a relative positioned box, 300px by 300px wide (‘width’ / ‘height’) moved in x-direction by 500px (‘left’ + position=‘relative’), decorated with a 10px solid black border.
Due to the absence of margins, the margin box coincides with the border box and is equal to (x, y, w, h) = (500px, 0px, 300px, 300px). Padding is absent, therefore the padding box coincides with the content box is equal to (510px, 10px, 280px, 280px). The content box defines the viewport for the inline SVG content.
The SVG content defines an intrinsic size of 400px by 200px (‘width’ / ‘height’) and is surrounded by a 2px dotted red border. The ‘margin-top’ property applied to the <svg> element, pushes the content box of the <svg> element in y-direction by 60px. Therefore the margin-box is equal to (0px, 60px, 400px, 200px) and the content box is equal to (2px, 62px, 396px, 196px).
The interplay of the ‘display’ / ‘position’ / ‘margin’ / ‘border’ / ‘padding’ / ‘width’ / etc. properties define the size/position of the <svg> element. However for painting / hit-testing / etc. the SVG subtree the user coordinate system is important, not the one defined by CSS. The user coordinate system is equal to the size of the viewport if ‘viewBox’ is omitted. Otherwise a transformation is computed that maps the desired coordinate system specified by the ‘viewBox’ attribute to the available content width/height, taking the ‘preserveAspectRatio’ attribute into account. Thus for the example above, the SVG content should be scaled by “content width / viewBox width” in x-direction and “content height / viewBox height” in y-direction. Therefore a “scale(396px / 10px = 39.6, 196px / 10px = 19.6)" transformation is applied before painting/hit-testing the RenderSVGRoot descendants.
The example above shall motivate the special role of RenderSVGRoot in the SVG Render Tree. To summarize, RenderSVGRoot has two facets: It needs to fulfil its role as replaced element in a parent CSS layout context (e.g. inline SVG embedded in HTML as in the example above). When parent renderers query RenderSVGRoot for e.g. geometry information, they need to be provided in the coordinate system established by CSS.
At the same time RenderSVGRoot needs to adhere to the SVG rules: it is responsible to establish a mapping between the SVG user coordinate system - that is used to render the descendants of RenderSVGRoot - and the CSS coordinate system. When RenderSVGRoot descendants query RenderSVGRoot for e.g. geometry information, they expect answers in the SVG user coordinate system.
RenderSVGForeignObject is the only other renderer, besides RenderSVGRoot that needs to care about CSS rules. Quoting from SVG2:
CSS positioning properties (e.g. top and margin) have no effect when positioning the embedded content element in the SVG coordinate system. They can, however, be used to position child elements of a ‘foreignObject’ or HTML embedding element.
Furthermore SVG2 discusses how RenderSVGForeignObject shall behave from CSS point of view:
The ‘foreignObject’, or other element that is positioned using SVG layout attributes, is implicitly absolutely-positioned for the purposes of CSS layout. As a result, any absolutely-positioned child elements are positioned relative to this containing block
At the moment these rules are not consistency enforced, as RenderSVGForeignObject does not create its own layer. If layer support is available for SVG renderers, implementing SVG <foreignObject> properly, is finally possible.