Bug 1610093 - Fix getScreenCTM when parent is transformed
--- a/dom/svg/SVGContentUtils.cpp
+++ b/dom/svg/SVGContentUtils.cpp
@@ -547,34 +547,57 @@ static gfx::Matrix GetCTMInternal(SVGEle
if (!ancestor || !ancestor->IsElement()) {
return gfx::ToMatrix(matrix);
}
if (ancestor->IsSVGElement()) {
return gfx::ToMatrix(matrix) *
GetCTMInternal(static_cast<SVGElement*>(ancestor), true, true);
}
- // XXX this does not take into account CSS transform, or that the non-SVG
- // content that we've hit may itself be inside an SVG foreignObject higher up
Document* currentDoc = aElement->GetComposedDoc();
+ PresShell* presShell = currentDoc ? currentDoc->GetPresShell() : nullptr;
float x = 0.0f, y = 0.0f;
- if (currentDoc &&
+ Matrix4x4Flagged transformToRoot;
+ SVGElement* nearestSVGAncestorAsSVGElement = nullptr;
+ if (presShell &&
element->NodeInfo()->Equals(nsGkAtoms::svg, kNameSpaceID_SVG)) {
- PresShell* presShell = currentDoc->GetPresShell();
- if (presShell) {
- nsIFrame* frame = element->GetPrimaryFrame();
- nsIFrame* ancestorFrame = presShell->GetRootFrame();
- if (frame && ancestorFrame) {
- nsPoint point = frame->GetOffsetTo(ancestorFrame);
- x = nsPresContext::AppUnitsToFloatCSSPixels(point.x);
- y = nsPresContext::AppUnitsToFloatCSSPixels(point.y);
- }
+ nsIContent* nearestSVGAncestor = ancestor;
+ while (nearestSVGAncestor && !nearestSVGAncestor->IsSVGElement()) {
+ nearestSVGAncestor = nearestSVGAncestor->GetFlattenedTreeParent();
+ }
+
+ nearestSVGAncestorAsSVGElement =
+ static_cast<SVGElement*>(nearestSVGAncestor);
+
+ nsIFrame* frame = element->GetPrimaryFrame();
+ nsIFrame* ancestorFrame = nearestSVGAncestor
+ ? nearestSVGAncestor->GetPrimaryFrame()
+ : presShell->GetRootFrame();
+ if (frame && ancestorFrame) {
+ transformToRoot = nsLayoutUtils::GetTransformToAncestor(
+ RelativeTo{frame, ViewportType::Layout},
+ RelativeTo{ancestorFrame, ViewportType::Layout},
+ nsIFrame::IN_CSS_UNITS);
+ nsPoint point = frame->GetOffsetTo(ancestorFrame);
+ x = nsPresContext::AppUnitsToFloatCSSPixels(point.x);
+ y = nsPresContext::AppUnitsToFloatCSSPixels(point.y);
}
}
- return ToMatrix(matrix).PostTranslate(x, y);
+ gfx::Matrix intermediateResult = ToMatrix(matrix);
+ gfx::Matrix result2d;
+ if (transformToRoot.CanDraw2D(&result2d)) {
+ intermediateResult = intermediateResult * result2d;
+ } else {
+ intermediateResult = intermediateResult.PostTranslate(x, y);
+ }
+ if (nearestSVGAncestorAsSVGElement) {
+ return intermediateResult *
+ GetCTMInternal(nearestSVGAncestorAsSVGElement, true, true);
+ }
+ return intermediateResult;
}
gfx::Matrix SVGContentUtils::GetCTM(SVGElement* aElement, bool aScreenCTM) {
return GetCTMInternal(aElement, aScreenCTM, false);
}
void SVGContentUtils::RectilinearGetStrokeBounds(
const Rect& aRect, const Matrix& aToBoundsSpace,
--- a/dom/svg/test/test_getCTM.html
+++ b/dom/svg/test/test_getCTM.html
@@ -93,18 +93,18 @@ function runTest() {
is((function() { try { return inner.getScreenCTM().f; } catch (e) { return e; } })(), 28, "inner.getScreenCTM().f");
// g1.farthestViewportElement == root
is((function() { try { return g1.getScreenCTM().e; } catch (e) { return e; } })(), 45, "g1.getScreenCTM().e");
is((function() { try { return g1.getScreenCTM().f; } catch (e) { return e; } })(), 68, "g1.getScreenCTM().f");
// outer.farthestViewportElement == null (but actually == root)
is((function() { try { return outer.getScreenCTM().e; } catch (e) { return e; } })(), 46, "outer.getScreenCTM().e");
is((function() { try { return outer.getScreenCTM().f; } catch (e) { return e; } })(), 69, "outer.getScreenCTM().f");
// outer.farthestViewportElement == null (but actually == root)
- is((function() { try { return outer2.getScreenCTM().e; } catch (e) { return e; } })(), -19, "outer2.getScreenCTM().e");
- is((function() { try { return outer2.getScreenCTM().f; } catch (e) { return e; } })(), -8, "outer2.getScreenCTM().f");
+ is((function() { try { return outer2.getScreenCTM().e; } catch (e) { return e; } })(), -4, "outer2.getScreenCTM().e");
+ is((function() { try { return outer2.getScreenCTM().f; } catch (e) { return e; } })(), 19, "outer2.getScreenCTM().f");
// g2.farthestViewportElement == outer (but actually == root)
is((function() { try { return g2.getScreenCTM().e; } catch (e) { return e; } })(), 646, "g2.getScreenCTM().e");
is((function() { try { return g2.getScreenCTM().f; } catch (e) { return e; } })(), 769, "g2.getScreenCTM().f");
// g3.farthestViewportElement == null (but actually == null)
is((function() { try { return g3.getScreenCTM(); } catch (e) { return e; } })(), null, "g3.getScreenCTM()");
// symbolRect.farthestViewportElement == root
is((function() { try { return symbolRect.getScreenCTM().e; } catch (e) { return e; } })(), 85, "symbolRect.getScreenCTM().e");
is((function() { try { return symbolRect.getScreenCTM().f; } catch (e) { return e; } })(), 108, "symbolRect.getScreenCTM().f");