Bug 1159053 - Cache SVG getBBox and objectBoundingBox calculations for better performance. r=heycam

This commit is contained in:
Jonathan Watt 2015-04-27 11:15:36 +01:00
parent 6e5096f6d3
commit 9922d273d9
5 changed files with 117 additions and 0 deletions

View File

@ -34,6 +34,7 @@ support-files =
[test_animLengthUnits.xhtml]
[test_bbox-with-invalid-viewBox.xhtml]
[test_bbox.xhtml]
[test_bbox-changes.xhtml]
[test_bounds.html]
[test_bug872812.html]
[test_getBBox-method.html]

View File

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1159053
-->
<head>
<title>Test that the results of getBBox update for changes</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display">
<svg xmlns="http://www.w3.org/2000/svg">
<rect id="rect1" x="10" y="10" width="10" height="10"/>
<rect id="rect2" x="30" y="10" width="10" height="10"/>
<g id="g">
<circle id="circle1" cx="60" cy="20" r="5"/>
<circle id="circle2" cx="120" cy="20" r="5"/>
</g>
</svg>
</p>
<div id="content" style="display: none"></div>
<pre id="test">
<script class="testbody" type="application/javascript">//<![CDATA[
SimpleTest.waitForExplicitFinish();
function init_and_run() {
SpecialPowers.pushPrefEnv({"set": [["svg.new-getBBox.enabled", true]]}, run);
}
function checkBBox(id, options, x, y, width, height, msg) {
var bbox = document.getElementById(id).getBBox(options);
is(bbox.x, x, id + ".getBBox().x" + msg);
is(bbox.y, y, id + ".getBBox().y" + msg);
is(bbox.width, width, id + ".getBBox().width" + msg);
is(bbox.height, height, id + ".getBBox().height" + msg);
}
function run()
{
// First call getBBox on 'rect1' with stroke included:
$("rect1").setAttribute("stroke", "black");
$("rect1").setAttribute("stroke-width", "10");
checkBBox("rect1", { fill:true, stroke:true }, 5, 5, 20, 20, " with stroke");
// Now remove the stroke from 'rect1' and check again:
$("rect1").removeAttribute("stroke");
$("rect1").removeAttribute("stroke-width");
checkBBox("rect1", { fill:true }, 10, 10, 10, 10, " after stroke removed");
// First call getBBox on 'rect2' without a stroke included:
checkBBox("rect2", { fill:true }, 30, 10, 10, 10, " with stroke");
// Now add a stroke to 'rect2' and check again:
$("rect2").setAttribute("stroke", "black");
$("rect2").setAttribute("stroke-width", "10");
checkBBox("rect2", { fill:true, stroke:true }, 25, 5, 20, 20, " with stroke");
// Check the initial result for getBBox on the group:
checkBBox("g", { fill:true }, 55, 15, 70, 10, " before child moves");
// Now move one of the circle children and check again:
$("circle2").setAttribute("cx", "110");
checkBBox("g", { fill:true }, 55, 15, 60, 10, " after child moves");
SimpleTest.finish();
}
window.addEventListener("load", init_and_run, false);
//]]></script>
</pre>
</body>
</html>

View File

@ -778,6 +778,9 @@ nsSVGEffects::InvalidateRenderingObservers(nsIFrame *aFrame)
if (!aFrame->GetContent()->IsElement())
return;
// If the rendering has changed, the bounds may well have changed too:
aFrame->Properties().Delete(nsSVGUtils::ObjectBoundingBoxProperty());
nsSVGRenderingObserverList *observerList =
GetObserverList(aFrame->GetContent()->AsElement());
if (observerList) {
@ -802,6 +805,12 @@ nsSVGEffects::InvalidateRenderingObservers(nsIFrame *aFrame)
void
nsSVGEffects::InvalidateDirectRenderingObservers(Element *aElement, uint32_t aFlags /* = 0 */)
{
nsIFrame* frame = aElement->GetPrimaryFrame();
if (frame) {
// If the rendering has changed, the bounds may well have changed too:
frame->Properties().Delete(nsSVGUtils::ObjectBoundingBoxProperty());
}
if (aElement->HasRenderingObservers()) {
nsSVGRenderingObserverList *observerList = GetObserverList(aElement);
if (observerList) {

View File

@ -903,6 +903,17 @@ nsSVGUtils::GetBBox(nsIFrame *aFrame, uint32_t aFlags)
!static_cast<const nsSVGElement*>(content)->HasValidDimensions()) {
return bbox;
}
FrameProperties props = aFrame->Properties();
if (aFlags == eBBoxIncludeFillGeometry) {
gfxRect* prop =
static_cast<gfxRect*>(props.Get(ObjectBoundingBoxProperty()));
if (prop) {
return *prop;
}
}
gfxMatrix matrix;
if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame ||
aFrame->GetType() == nsGkAtoms::svgUseFrame) {
@ -971,6 +982,13 @@ nsSVGUtils::GetBBox(nsIFrame *aFrame, uint32_t aFlags)
bbox = gfxRect(0, 0, 0, 0);
}
}
if (aFlags == eBBoxIncludeFillGeometry) {
// Obtaining the bbox for objectBoundingBox calculations is common so we
// cache the result for future calls, since calculation can be expensive:
props.Set(ObjectBoundingBoxProperty(), new gfxRect(bbox));
}
return bbox;
}
return nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(aFrame);

View File

@ -21,6 +21,7 @@
#include "nsColor.h"
#include "nsCOMPtr.h"
#include "nsID.h"
#include "nsIFrame.h"
#include "nsISupportsBase.h"
#include "nsMathUtils.h"
#include "nsStyleStruct.h"
@ -179,12 +180,20 @@ class nsSVGUtils
{
public:
typedef mozilla::dom::Element Element;
typedef mozilla::FramePropertyDescriptor FramePropertyDescriptor;
typedef mozilla::gfx::AntialiasMode AntialiasMode;
typedef mozilla::gfx::FillRule FillRule;
typedef mozilla::gfx::GeneralPattern GeneralPattern;
static void Init();
static void DestroyObjectBoundingBoxProperty(void* aPropertyValue) {
delete static_cast<gfxRect*>(aPropertyValue);
}
NS_DECLARE_FRAME_PROPERTY(ObjectBoundingBoxProperty,
DestroyObjectBoundingBoxProperty);
/**
* Gets the nearest nsSVGInnerSVGFrame or nsSVGOuterSVGFrame frame. aFrame
* must be an SVG frame. If aFrame is of type nsGkAtoms::svgOuterSVGFrame,
@ -394,6 +403,8 @@ public:
* aFrame's userspace.
*/
static gfxRect GetBBox(nsIFrame *aFrame,
// If the default arg changes, update the handling for
// ObjectBoundingBoxProperty() in the implementation.
uint32_t aFlags = eBBoxIncludeFillGeometry);
/*