Bug 1610093 - Fix getScreenCTM when parent is transformed draft bug1610093
authorTimothy Nikkel <tnikkel@gmail.com>
Sun, 16 Jan 2022 11:56:43 +0000
changeset 4172417 9f660edd3cccfdd9e7750b1dc9e59798191f1577
parent 4172416 9ef0614a59629916c1e182eb8eda055b0b0e8b32
child 4261749 0a07379891af8a2efcfbc72514e2ff8f064b5b98
push id769488
push userlongsonr@gmail.com
push dateSun, 16 Jan 2022 12:00:26 +0000
treeherdertry@9f660edd3ccc [default view] [failures only]
bugs1610093
milestone98.0a1
Bug 1610093 - Fix getScreenCTM when parent is transformed
dom/svg/SVGContentUtils.cpp
dom/svg/test/test_getCTM.html
toolkit/themes/shared/icons/reload.svg
--- 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");