Bug 366697 - getCTM() shouldn't return the same matrix as .getScreenCTM() for nested SVG elements. r=longsonr
authorRyo Onodera <ryoqun@gmail.com>
Fri, 26 Jun 2009 21:13:07 +0100
changeset 29610 eeb04deff8f3d83265f416c1ef8db24debe86b1f
parent 29609 d3f3e93bf841a50d72ad7a357dc196864af6a919
child 29611 37fd13fb79cfcddf2d6370329dd5b1b45a3febc7
push idunknown
push userunknown
push dateunknown
reviewerslongsonr
bugs366697
milestone1.9.2a1pre
Bug 366697 - getCTM() shouldn't return the same matrix as .getScreenCTM() for nested SVG elements. r=longsonr
content/svg/content/src/nsSVGElement.cpp
content/svg/content/src/nsSVGForeignObjectElement.cpp
content/svg/content/src/nsSVGForeignObjectElement.h
content/svg/content/src/nsSVGGraphicElement.cpp
content/svg/content/src/nsSVGGraphicElement.h
content/svg/content/src/nsSVGSVGElement.cpp
content/svg/content/src/nsSVGSVGElement.h
content/svg/content/test/Makefile.in
content/svg/content/test/getCTM-helper.svg
content/svg/content/test/test_getCTM.html
content/svg/content/test/test_viewport.html
layout/svg/base/src/nsSVGUtils.cpp
layout/svg/base/src/nsSVGUtils.h
--- a/content/svg/content/src/nsSVGElement.cpp
+++ b/content/svg/content/src/nsSVGElement.cpp
@@ -927,17 +927,18 @@ nsSVGElement::GetOwnerSVGElement(nsIDOMS
   // this situation can e.g. occur during content tree teardown. 
   return NS_ERROR_FAILURE;
 }
 
 /* readonly attribute nsIDOMSVGElement viewportElement; */
 NS_IMETHODIMP
 nsSVGElement::GetViewportElement(nsIDOMSVGElement * *aViewportElement)
 {
-  return nsSVGUtils::GetNearestViewportElement(this, aViewportElement);
+  nsSVGUtils::GetNearestViewportElement(this, aViewportElement);
+  return NS_OK; // we can't throw exceptions from this API.
 }
 
 //----------------------------------------------------------------------
 // nsISVGValueObserver methods:
 
 NS_IMETHODIMP
 nsSVGElement::WillModifySVGObservable(nsISVGValue* observable,
                                       nsISVGValue::modificationType aModType)
--- a/content/svg/content/src/nsSVGForeignObjectElement.cpp
+++ b/content/svg/content/src/nsSVGForeignObjectElement.cpp
@@ -33,16 +33,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsCOMPtr.h"
 #include "nsSVGForeignObjectElement.h"
+#include "nsSVGMatrix.h"
 
 nsSVGElement::LengthInfo nsSVGForeignObjectElement::sLengthInfo[4] =
 {
   { &nsGkAtoms::x, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, nsSVGUtils::X },
   { &nsGkAtoms::y, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, nsSVGUtils::Y },
   { &nsGkAtoms::width, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, nsSVGUtils::X },
   { &nsGkAtoms::height, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, nsSVGUtils::Y },
 };
@@ -111,16 +112,35 @@ nsSVGForeignObjectElement::PrependLocalT
   gfxMatrix matrix = nsSVGForeignObjectElementBase::PrependLocalTransformTo(aMatrix);
   
   // now translate by our 'x' and 'y':
   float x, y;
   GetAnimatedLengthValues(&x, &y, nsnull);
   return gfxMatrix().Translate(gfxPoint(x, y)) * matrix;
 }
 
+nsresult
+nsSVGForeignObjectElement::AppendTransform(nsIDOMSVGMatrix *aCTM,
+                                           nsIDOMSVGMatrix **_retval)
+{
+  nsresult rv;
+  // foreignObject is one of establishing-viewport elements.
+  // so we are translated by foreignObject's x and y attribs.
+  float x, y;
+  GetAnimatedLengthValues(&x, &y, nsnull);
+  nsCOMPtr<nsIDOMSVGMatrix> translate;
+  rv = NS_NewSVGMatrix(getter_AddRefs(translate), 1, 0, 0, 1, x, y);
+  if (NS_FAILED(rv)) return rv;
+  nsCOMPtr<nsIDOMSVGMatrix> tmp;
+  rv = aCTM->Multiply(translate, getter_AddRefs(tmp));
+  if (NS_FAILED(rv)) return rv;
+
+  return nsSVGGraphicElement::AppendTransform(tmp, _retval);
+}
+
 //----------------------------------------------------------------------
 // nsIContent methods
 
 NS_IMETHODIMP_(PRBool)
 nsSVGForeignObjectElement::IsAttributeMapped(const nsIAtom* name) const
 {
   static const MappedAttributeEntry* const map[] = {
     sFEFloodMap,
--- a/content/svg/content/src/nsSVGForeignObjectElement.h
+++ b/content/svg/content/src/nsSVGForeignObjectElement.h
@@ -69,16 +69,19 @@ public:
   // nsSVGElement specializations:
   virtual gfxMatrix PrependLocalTransformTo(const gfxMatrix &aMatrix);
 
   // nsIContent interface
   NS_IMETHOD_(PRBool) IsAttributeMapped(const nsIAtom* name) const;
 
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
 
+  // public helpers
+  nsresult AppendTransform(nsIDOMSVGMatrix *aCTM,
+                           nsIDOMSVGMatrix **_retval);
 protected:
 
   virtual LengthAttributesInfo GetLengthInfo();
   
   enum { X, Y, WIDTH, HEIGHT };
   nsSVGLength2 mLengthAttributes[4];
   static LengthInfo sLengthInfo[4];
 };
--- a/content/svg/content/src/nsSVGGraphicElement.cpp
+++ b/content/svg/content/src/nsSVGGraphicElement.cpp
@@ -70,17 +70,18 @@ nsSVGGraphicElement::nsSVGGraphicElement
 }
 
 //----------------------------------------------------------------------
 // nsIDOMSVGLocatable methods
 
 /* readonly attribute nsIDOMSVGElement nearestViewportElement; */
 NS_IMETHODIMP nsSVGGraphicElement::GetNearestViewportElement(nsIDOMSVGElement * *aNearestViewportElement)
 {
-  return nsSVGUtils::GetNearestViewportElement(this, aNearestViewportElement);
+  nsSVGUtils::GetNearestViewportElement(this, aNearestViewportElement);
+  return NS_OK; // we can't throw exceptions from this API.
 }
 
 /* readonly attribute nsIDOMSVGElement farthestViewportElement; */
 NS_IMETHODIMP nsSVGGraphicElement::GetFarthestViewportElement(nsIDOMSVGElement * *aFarthestViewportElement)
 {
   return nsSVGUtils::GetFarthestViewportElement(this, aFarthestViewportElement);
 }
 
@@ -98,18 +99,18 @@ NS_IMETHODIMP nsSVGGraphicElement::GetBB
   if (svgframe) {
     return NS_NewSVGRect(_retval, nsSVGUtils::GetBBox(frame));
   }
   return NS_ERROR_FAILURE;
 }
 
 /* Helper for GetCTM and GetScreenCTM */
 nsresult
-nsSVGGraphicElement::AppendLocalTransform(nsIDOMSVGMatrix *aCTM,
-                                          nsIDOMSVGMatrix **_retval)
+nsSVGGraphicElement::AppendTransform(nsIDOMSVGMatrix *aCTM,
+                                     nsIDOMSVGMatrix **_retval)
 {
   if (!mTransforms) {
     *_retval = aCTM;
     NS_ADDREF(*_retval);
     return NS_OK;
   }
 
   // append our local transformations
@@ -122,71 +123,25 @@ nsSVGGraphicElement::AppendLocalTransfor
     *_retval = aCTM;
     NS_ADDREF(*_retval);
     return NS_OK;
   }
   return aCTM->Multiply(matrix, _retval);  // addrefs, so we don't
 }
 
 /* nsIDOMSVGMatrix getCTM (); */
-NS_IMETHODIMP nsSVGGraphicElement::GetCTM(nsIDOMSVGMatrix **_retval)
+NS_IMETHODIMP nsSVGGraphicElement::GetCTM(nsIDOMSVGMatrix * *aCTM)
 {
-  nsresult rv;
-  *_retval = nsnull;
-
-  nsIDocument* currentDoc = GetCurrentDoc();
-  if (currentDoc) {
-    // Flush all pending notifications so that our frames are uptodate
-    currentDoc->FlushPendingNotifications(Flush_Layout);
-  }
-
-  nsIContent* parent = nsSVGUtils::GetParentElement(this);
-
-  nsCOMPtr<nsIDOMSVGLocatable> locatableElement = do_QueryInterface(parent);
-  if (!locatableElement) {
-    // we don't have an SVGLocatable parent so we aren't even rendered
-    NS_WARNING("SVGGraphicElement without an SVGLocatable parent");
-    return NS_ERROR_FAILURE;
-  }
-
-  // get our parent's CTM
-  nsCOMPtr<nsIDOMSVGMatrix> parentCTM;
-  rv = locatableElement->GetCTM(getter_AddRefs(parentCTM));
-  if (NS_FAILED(rv)) return rv;
-
-  return AppendLocalTransform(parentCTM, _retval);
+  return nsSVGUtils::GetCTM(this, aCTM);
 }
 
 /* nsIDOMSVGMatrix getScreenCTM (); */
-NS_IMETHODIMP nsSVGGraphicElement::GetScreenCTM(nsIDOMSVGMatrix **_retval)
+NS_IMETHODIMP nsSVGGraphicElement::GetScreenCTM(nsIDOMSVGMatrix * *aCTM)
 {
-  nsresult rv;
-  *_retval = nsnull;
-
-  nsIDocument* currentDoc = GetCurrentDoc();
-  if (currentDoc) {
-    // Flush all pending notifications so that our frames are uptodate
-    currentDoc->FlushPendingNotifications(Flush_Layout);
-  }
-
-  nsIContent* parent = nsSVGUtils::GetParentElement(this);
-
-  nsCOMPtr<nsIDOMSVGLocatable> locatableElement = do_QueryInterface(parent);
-  if (!locatableElement) {
-    // we don't have an SVGLocatable parent so we aren't even rendered
-    NS_WARNING("SVGGraphicElement without an SVGLocatable parent");
-    return NS_ERROR_FAILURE;
-  }
-
-  // get our parent's "screen" CTM
-  nsCOMPtr<nsIDOMSVGMatrix> parentScreenCTM;
-  rv = locatableElement->GetScreenCTM(getter_AddRefs(parentScreenCTM));
-  if (NS_FAILED(rv)) return rv;
-
-  return AppendLocalTransform(parentScreenCTM, _retval);
+  return nsSVGUtils::GetScreenCTM(this, aCTM);
 }
 
 /* nsIDOMSVGMatrix getTransformToElement (in nsIDOMSVGElement element); */
 NS_IMETHODIMP nsSVGGraphicElement::GetTransformToElement(nsIDOMSVGElement *element, nsIDOMSVGMatrix **_retval)
 {
   if (!element)
     return NS_ERROR_DOM_SVG_WRONG_TYPE_ERR;
 
--- a/content/svg/content/src/nsSVGGraphicElement.h
+++ b/content/svg/content/src/nsSVGGraphicElement.h
@@ -59,24 +59,25 @@ public:
   NS_DECL_NSIDOMSVGLOCATABLE
   NS_DECL_NSIDOMSVGTRANSFORMABLE
 
   // nsIContent interface
   NS_IMETHOD_(PRBool) IsAttributeMapped(const nsIAtom* aAttribute) const;
 
   virtual gfxMatrix PrependLocalTransformTo(const gfxMatrix &aMatrix);
 
+  // public helpers
+  virtual nsresult AppendTransform(nsIDOMSVGMatrix *aCTM,
+                                   nsIDOMSVGMatrix **_retval);
 protected:
   // nsSVGElement overrides
   virtual PRBool IsEventName(nsIAtom* aName);
   
   virtual nsresult BeforeSetAttr(PRInt32 aNamespaceID, nsIAtom* aName,
                                  const nsAString* aValue, PRBool aNotify);
 
   nsCOMPtr<nsIDOMSVGAnimatedTransformList> mTransforms;
 
   // helper
   nsresult CreateTransformList();
-  nsresult AppendLocalTransform(nsIDOMSVGMatrix *aCTM,
-                                nsIDOMSVGMatrix **_retval);
 };
 
 #endif // __NS_SVGGRAPHICELEMENT_H__
--- a/content/svg/content/src/nsSVGSVGElement.cpp
+++ b/content/svg/content/src/nsSVGSVGElement.cpp
@@ -741,17 +741,18 @@ nsSVGSVGElement::GetPreserveAspectRatio(
 
 //----------------------------------------------------------------------
 // nsIDOMSVGLocatable methods
 
 /* readonly attribute nsIDOMSVGElement nearestViewportElement; */
 NS_IMETHODIMP
 nsSVGSVGElement::GetNearestViewportElement(nsIDOMSVGElement * *aNearestViewportElement)
 {
-  return nsSVGUtils::GetNearestViewportElement(this, aNearestViewportElement);
+  nsSVGUtils::GetNearestViewportElement(this, aNearestViewportElement);
+  return NS_OK; // we can't throw exceptions from this API.
 }
 
 /* readonly attribute nsIDOMSVGElement farthestViewportElement; */
 NS_IMETHODIMP
 nsSVGSVGElement::GetFarthestViewportElement(nsIDOMSVGElement * *aFarthestViewportElement)
 {
   return nsSVGUtils::GetFarthestViewportElement(this, aFarthestViewportElement);
 }
@@ -769,215 +770,78 @@ nsSVGSVGElement::GetBBox(nsIDOMSVGRect *
 
   nsISVGChildFrame* svgframe = do_QueryFrame(frame);
   if (svgframe) {
     return NS_NewSVGRect(_retval, nsSVGUtils::GetBBox(frame));
   }
   return NS_ERROR_NOT_IMPLEMENTED; // XXX: outer svg
 }
 
-/* nsIDOMSVGMatrix getCTM (); */
-NS_IMETHODIMP
-nsSVGSVGElement::GetCTM(nsIDOMSVGMatrix **_retval)
+nsresult
+nsSVGSVGElement::AppendTransform(nsIDOMSVGMatrix *aCTM,
+                                 nsIDOMSVGMatrix **_retval)
 {
   nsresult rv;
-  *_retval = nsnull;
+
+  // first check what are our parents and calculate offsets accordingly.
 
-  nsIDocument* currentDoc = GetCurrentDoc();
-  if (currentDoc) {
-    // Flush all pending notifications so that our frames are uptodate
-    currentDoc->FlushPendingNotifications(Flush_Layout);
-  }
-
-  // first try to get the "screen" CTM of our nearest SVG ancestor
-
-  nsCOMPtr<nsIContent> element = this;
-  nsCOMPtr<nsIContent> ancestor;
-  unsigned short ancestorCount = 0;
-  nsCOMPtr<nsIDOMSVGMatrix> ancestorCTM;
+  float s=1, x=0, y=0;
+  nsIContent *ancestor = nsSVGUtils::GetParentElement(this);
+  if (ancestor && ancestor->GetNameSpaceID() == kNameSpaceID_SVG &&
+                  ancestor->Tag() == nsGkAtoms::foreignObject) {
+    // this is a nested <svg> element. immediate parent is an <foreignObject> element.
+    // we ignore this <svg> element's x and y attribs in layout so do the same.
+  } else {
+    nsCOMPtr<nsIDOMSVGElement> nearestViewportElement;
+    rv = nsSVGUtils::GetNearestViewportElement(this, getter_AddRefs(nearestViewportElement));
+    if (NS_FAILED(rv)) return rv;
 
-  while (1) {
-    ancestor = nsSVGUtils::GetParentElement(element);
-    if (!ancestor) {
-      // reached the top of our parent chain without finding an SVG ancestor
-      break;
+    if (!nearestViewportElement) {
+      if (IsRoot()) {
+        // we're the root element. get our currentScale and currentTranslate vals
+        s = mCurrentScale;
+        x = mCurrentTranslate.GetX();
+        y = mCurrentTranslate.GetY();
+      } else {
+        // we're inline in some non-SVG content. get our offset from the root
+        GetOffsetToAncestor(nsnull, x, y);
+      }
+    } else {
+      // this is a nested <svg> element.
+      GetAnimatedLengthValues(&x, &y, nsnull);
     }
-
-    nsSVGSVGElement *viewportElement = QI_AND_CAST_TO_NSSVGSVGELEMENT(ancestor);
-    if (viewportElement) {
-      rv = viewportElement->GetViewboxToViewportTransform(getter_AddRefs(ancestorCTM));
-      if (NS_FAILED(rv)) return rv;
-      break;
-    }
-
-    nsCOMPtr<nsIDOMSVGLocatable> locatableElement = do_QueryInterface(ancestor);
-    if (locatableElement) {
-      rv = locatableElement->GetCTM(getter_AddRefs(ancestorCTM));
-      if (NS_FAILED(rv)) return rv;
-      break;
-    }
-
-    // ancestor was not SVG content. loop until we find an SVG ancestor
-    element = ancestor;
-    ancestorCount++;
   }
 
-  // now account for our offset
-
-  if (!ancestorCTM) {
-    // we didn't find an SVG ancestor
-    float s=1, x=0, y=0;
-    if (IsRoot()) {
-      // we're the root element. get our currentScale and currentTranslate vals
-      s = mCurrentScale;
-      x = mCurrentTranslate.GetX();
-      y = mCurrentTranslate.GetY();
-    }
-    else {
-      // we're inline in some non-SVG content. get our offset from the root
-      GetOffsetToAncestor(nsnull, x, y);
-    }
-    rv = NS_NewSVGMatrix(getter_AddRefs(ancestorCTM), s, 0, 0, s, x, y);
-    if (NS_FAILED(rv)) return rv;
-  }
-  else {
-    // we found an SVG ancestor
-    float x=0, y=0;
-    nsCOMPtr<nsIDOMSVGMatrix> tmp;
-    if (ancestorCount == 0) {
-      // our immediate parent is an SVG element. get our 'x' and 'y' attribs.
-      // cast to nsSVGElement so we get our ancestor coord context.
-      x = mLengthAttributes[X].GetAnimValue(static_cast<nsSVGElement*>
-                                                       (this));
-      y = mLengthAttributes[Y].GetAnimValue(static_cast<nsSVGElement*>
-                                                       (this));
-    }
-    else {
-      // We have an SVG ancestor, but with non-SVG content between us
-#if 0
-      nsCOMPtr<nsIDOMSVGForeignObjectElement> foreignObject
-                                              = do_QueryInterface(ancestor);
-      if (!foreignObject) {
-        NS_ERROR("the none-SVG content in the parent chain between us and our "
-                 "SVG ancestor isn't rooted in a foreignObject element");
-        return NS_ERROR_FAILURE;
-      }
-#endif
-      // XXXjwatt: this isn't quite right since foreignObject can transform its
-      // content, but it's close enough until we turn foreignObject back on
-      GetOffsetToAncestor(ancestor, x, y);
-    }
-    rv = ancestorCTM->Translate(x, y, getter_AddRefs(tmp));
-    if (NS_FAILED(rv)) return rv;
-    ancestorCTM.swap(tmp);
-  }
+  nsCOMPtr<nsIDOMSVGMatrix> local;
+  rv = NS_NewSVGMatrix(getter_AddRefs(local), s, 0, 0, s, x, y);
+  if (NS_FAILED(rv)) return rv;
 
   // finally append our viewbox transform
 
+  nsCOMPtr<nsIDOMSVGMatrix> viewbox;
+  rv = GetViewboxToViewportTransform(getter_AddRefs(viewbox));
+  if (NS_FAILED(rv)) return rv;
   nsCOMPtr<nsIDOMSVGMatrix> tmp;
-  rv = GetViewboxToViewportTransform(getter_AddRefs(tmp));
+  rv = local->Multiply(viewbox, getter_AddRefs(tmp)); // addrefs, so we don't
   if (NS_FAILED(rv)) return rv;
-  return ancestorCTM->Multiply(tmp, _retval);  // addrefs, so we don't
+  return aCTM->Multiply(tmp, _retval); // addrefs, so we don't
+}
+
+/* nsIDOMSVGMatrix getCTM (); */
+NS_IMETHODIMP
+nsSVGSVGElement::GetCTM(nsIDOMSVGMatrix * *aCTM)
+{
+  return nsSVGUtils::GetCTM(this, aCTM);
 }
 
 /* nsIDOMSVGMatrix getScreenCTM (); */
 NS_IMETHODIMP
-nsSVGSVGElement::GetScreenCTM(nsIDOMSVGMatrix **_retval)
+nsSVGSVGElement::GetScreenCTM(nsIDOMSVGMatrix **aCTM)
 {
-  nsresult rv;
-  *_retval = nsnull;
-
-  nsIDocument* currentDoc = GetCurrentDoc();
-  if (currentDoc) {
-    // Flush all pending notifications so that our frames are uptodate
-    currentDoc->FlushPendingNotifications(Flush_Layout);
-  }
-
-  // first try to get the "screen" CTM of our nearest SVG ancestor
-
-  nsCOMPtr<nsIContent> element = this;
-  nsCOMPtr<nsIContent> ancestor;
-  unsigned short ancestorCount = 0;
-  nsCOMPtr<nsIDOMSVGMatrix> ancestorScreenCTM;
-
-  while (1) {
-    ancestor = nsSVGUtils::GetParentElement(element);
-    if (!ancestor) {
-      // reached the top of our parent chain without finding an SVG ancestor
-      break;
-    }
-
-    nsCOMPtr<nsIDOMSVGLocatable> locatableElement = do_QueryInterface(ancestor);
-    if (locatableElement) {
-      rv = locatableElement->GetScreenCTM(getter_AddRefs(ancestorScreenCTM));
-      if (NS_FAILED(rv)) return rv;
-      break;
-    }
-
-    // ancestor was not SVG content. loop until we find an SVG ancestor
-    element = ancestor;
-    ancestorCount++;
-  }
-
-  // now account for our offset
-
-  if (!ancestorScreenCTM) {
-    // we didn't find an SVG ancestor
-    float s=1, x=0, y=0;
-    if (IsRoot()) {
-      // we're the root element. get our currentScale and currentTranslate vals
-      s = mCurrentScale;
-      x = mCurrentTranslate.GetX();
-      y = mCurrentTranslate.GetY();
-    }
-    else {
-      // we're inline in some non-SVG content. get our offset from the root
-      GetOffsetToAncestor(nsnull, x, y);
-    }
-    rv = NS_NewSVGMatrix(getter_AddRefs(ancestorScreenCTM), s, 0, 0, s, x, y);
-    if (NS_FAILED(rv)) return rv;
-  }
-  else {
-    // we found an SVG ancestor
-    float x=0, y=0;
-    nsCOMPtr<nsIDOMSVGMatrix> tmp;
-    if (ancestorCount == 0) {
-      // our immediate parent is an SVG element. get our 'x' and 'y' attribs
-      // cast to nsSVGElement so we get our ancestor coord context.
-      x = mLengthAttributes[X].GetAnimValue(static_cast<nsSVGElement*>
-                                                       (this));
-      y = mLengthAttributes[Y].GetAnimValue(static_cast<nsSVGElement*>
-                                                       (this));
-    }
-    else {
-      // We have an SVG ancestor, but with non-SVG content between us
-#if 0
-      nsCOMPtr<nsIDOMSVGForeignObjectElement> foreignObject
-                                              = do_QueryInterface(ancestor);
-      if (!foreignObject) {
-        NS_ERROR("the none-SVG content in the parent chain between us and our "
-                 "SVG ancestor isn't rooted in a foreignObject element");
-        return NS_ERROR_FAILURE;
-      }
-#endif
-      // XXXjwatt: this isn't quite right since foreignObject can transform its
-      // content, but it's close enough until we turn foreignObject back on
-      GetOffsetToAncestor(ancestor, x, y);
-    }
-    rv = ancestorScreenCTM->Translate(x, y, getter_AddRefs(tmp));
-    if (NS_FAILED(rv)) return rv;
-    ancestorScreenCTM.swap(tmp);
-  }
-
-  // finally append our viewbox transform
-
-  nsCOMPtr<nsIDOMSVGMatrix> tmp;
-  rv = GetViewboxToViewportTransform(getter_AddRefs(tmp));
-  if (NS_FAILED(rv)) return rv;
-  return ancestorScreenCTM->Multiply(tmp, _retval);  // addrefs, so we don't
+  return nsSVGUtils::GetScreenCTM(this, aCTM);
 }
 
 /* nsIDOMSVGMatrix getTransformToElement (in nsIDOMSVGElement element); */
 NS_IMETHODIMP
 nsSVGSVGElement::GetTransformToElement(nsIDOMSVGElement *element,
                                        nsIDOMSVGMatrix **_retval)
 {
   if (!element)
--- a/content/svg/content/src/nsSVGSVGElement.h
+++ b/content/svg/content/src/nsSVGSVGElement.h
@@ -195,16 +195,18 @@ public:
   virtual void DidChangePreserveAspectRatio(PRBool aDoSetAttr);
 
   // nsSVGSVGElement methods:
   float GetLength(PRUint8 mCtxType);
   float GetMMPerPx(PRUint8 mCtxType = 0);
 
   // public helpers:
   nsresult GetViewboxToViewportTransform(nsIDOMSVGMatrix **_retval);
+  nsresult AppendTransform(nsIDOMSVGMatrix *aCTM,
+                           nsIDOMSVGMatrix **_retval);
 
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
 
   svgFloatSize GetViewportSize() const {
     return svgFloatSize(mViewportWidth, mViewportHeight);
   }
 
   void SetViewportSize(const svgFloatSize& aSize) {
--- a/content/svg/content/test/Makefile.in
+++ b/content/svg/content/test/Makefile.in
@@ -45,16 +45,18 @@ include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
 		test_bbox.xhtml \
 		bbox-helper.svg \
 		bounds-helper.svg \
 		test_dataTypes.html \
 		dataTypes-helper.svg \
+		getCTM-helper.svg \
+		test_getCTM.html \
 		test_getSubStringLength.xhtml \
 		getSubStringLength-helper.svg \
 		test_pathSeg.xhtml \
 		test_scientific.html \
 		scientific-helper.svg \
 		test_switch.xhtml \
 		switch-helper.svg \
 		test_text.html \
new file mode 100644
--- /dev/null
+++ b/content/svg/content/test/getCTM-helper.svg
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="100" height="100" viewBox="-11 -22 100 100">
+  <g transform="translate(3, 4)">
+  <svg x="1" y="2" width="888" height="999">
+  <g>
+    <svg x="30" y="40" width="100" height="100">
+      <g id="buggy"/>
+    </svg>
+
+    <defs>
+      <symbol id="sym" width="100" height="100">
+        <rect id="symbolRect" width="0" height="0"
+              transform="translate(70, 80)"/>
+      </symbol>
+    </defs>
+    <svg id="inner" x="30" y="40" width="100" height="100">
+      <g id="g1"/>
+    </svg>
+    <foreignObject x="30" y="40" width="100" height="100" transform="translate(1, 1)">
+      <!-- current layout implementation ignores x="50" and y="60".
+           thus, I made getCTM and getScreenCTM do the same. -->
+      <svg id="outer" x="50" y="60" width="100" height="100">
+        <g id="g2" transform="translate(600, 700)"/>
+      </svg>
+    </foreignObject>
+    <!-- something invalid -->
+    <foreignObject>
+      <g id="g3"/>
+    </foreignObject>
+    <use xlink:href="#sym" id="use"/>
+  </g>
+  </svg>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/content/svg/content/test/test_getCTM.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=366697
+-->
+<head>
+  <title>Test for Bug 366697</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=366697">Mozilla Bug 366697</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+
+<iframe id="svg" src="getCTM-helper.svg"></iframe>
+
+<pre id="test">
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function runTest()
+{
+  var doc = $("svg").contentWindow.document;
+
+  /* Minimal */
+  var buggy = doc.getElementById("buggy");
+  is(buggy.getCTM().e, 30, "buggy.getCTM().e");
+  is(buggy.getCTM().f, 40, "buggy.getCTM().f");
+
+  var root = doc.documentElement;
+  var inner = doc.getElementById("inner");
+  var g1 = doc.getElementById("g1");
+  var outer = doc.getElementById("outer");
+  var g2 = doc.getElementById("g2");
+  var g3 = doc.getElementById("g3");
+  var sym = doc.getElementById("sym");
+  var symbolRect = doc.getElementById("symbolRect");
+  /* Tests the consistency with nearestViewportElement
+     (code is from test_viewport.html) */
+  // root.nearestViewportElement == null
+  is((function(){try{return root.getCTM()}catch(e){return e}})(), null, "root.getCTM()");
+  // inner.nearestViewportElement == root
+  is((function(){try{return inner.getCTM().e}catch(e){return e}})(), 31, "inner.getCTM().e");
+  is((function(){try{return inner.getCTM().f}catch(e){return e}})(), 42, "inner.getCTM().f");
+  // g1.nearestViewportElement == inner
+  is((function(){try{return g1.getCTM().e}catch(e){return e}})(), 30, "g1.getCTM().e");
+  is((function(){try{return g1.getCTM().f}catch(e){return e}})(), 40, "g1.getCTM().f");
+  // outer.nearestViewportElement == null
+  is((function(){try{return outer.getCTM()}catch(e){return e}})(), null, "outer.getCTM()");
+  // g2.nearestViewportElement == outer
+  is((function(){try{return g2.getCTM().e}catch(e){return e}})(), 600, "g2.getCTM().e");
+  is((function(){try{return g2.getCTM().f}catch(e){return e}})(), 700, "g2.getCTM().f");
+  // g3.nearestViewportElement == null
+  is((function(){try{return g3.getCTM()}catch(e){return e}})(), null, "g3.getCTM()");
+  // symbolRect.nearestViewportElement == sym
+  is((function(){try{return symbolRect.getCTM().e}catch(e){return e}})(), 70, "symbolRect.getCTM().e");
+  is((function(){try{return symbolRect.getCTM().f}catch(e){return e}})(), 80, "symbolRect.getCTM().f");
+
+  /* Tests the consistency with farthestViewportElement
+     (code is from test_viewport.html) */
+  // root.farthestViewportElement == null (but actually == root)
+  is((function(){try{return root.getScreenCTM().e}catch(e){return e}})(), 11, "root.getScreenCTM().e");
+  is((function(){try{return root.getScreenCTM().f}catch(e){return e}})(), 22, "root.getScreenCTM().f");
+  // inner.farthestViewportElement == root
+  is((function(){try{return inner.getScreenCTM().e}catch(e){return e}})(), 45, "inner.getScreenCTM().e");
+  is((function(){try{return inner.getScreenCTM().f}catch(e){return e}})(), 68, "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");
+  // 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");
+
+  SimpleTest.finish();
+}
+
+window.addEventListener("load", runTest, false);
+</script>
+</pre>
+</body>
+</html>
--- a/content/svg/content/test/test_viewport.html
+++ b/content/svg/content/test/test_viewport.html
@@ -34,17 +34,17 @@ function runTest()
   var symbolRect = doc.getElementById("symbolRect");
 
   <!-- viewportElement -->
   is(root.viewportElement, null, "root.viewportElement");
   is(inner.viewportElement, root, "inner.viewportElement");
   is(g1.viewportElement, inner, "g1.viewportElement");
   is(outer.viewportElement, null, "outer.viewportElement");
   is(g2.viewportElement, outer, "g2.viewportElement");
-  is(g3.viewportElement, null, "g2.viewportElement");
+  is(g3.viewportElement, null, "g3.viewportElement");
   is(symbolRect.viewportElement, sym, "symbolRect.viewportElement");
 
   <!-- nearestViewportElement -->
   is(root.nearestViewportElement, null, "root.nearestViewportElement");
   is(inner.nearestViewportElement, root, "inner.nearestViewportElement");
   is(g1.nearestViewportElement, inner, "g1.nearestViewportElement");
   is(outer.nearestViewportElement, null, "outer.nearestViewportElement");
   is(g2.nearestViewportElement, outer, "g2.nearestViewportElement");
--- a/layout/svg/base/src/nsSVGUtils.cpp
+++ b/layout/svg/base/src/nsSVGUtils.cpp
@@ -64,16 +64,17 @@
 #include "nsSVGInnerSVGFrame.h"
 #include "nsSVGPreserveAspectRatio.h"
 #include "nsSVGMatrix.h"
 #include "nsSVGClipPathFrame.h"
 #include "nsSVGMaskFrame.h"
 #include "nsSVGContainerFrame.h"
 #include "nsSVGLength2.h"
 #include "nsGenericElement.h"
+#include "nsSVGGraphicElement.h"
 #include "nsAttrValue.h"
 #include "nsSVGGeometryFrame.h"
 #include "nsIScriptError.h"
 #include "gfxContext.h"
 #include "gfxMatrix.h"
 #include "gfxRect.h"
 #include "gfxImageSurface.h"
 #include "gfxPlatform.h"
@@ -452,20 +453,114 @@ nsSVGUtils::GetNearestViewportElement(ns
       // right interface
       nsCOMPtr<nsIDOMSVGElement> element = do_QueryInterface(ancestor);
       element.swap(*aNearestViewportElement);
       return NS_OK;
     }
 
     ancestor = GetParentElement(ancestor);
   }
+  if (ancestor && ancestor->GetNameSpaceID() == kNameSpaceID_SVG &&
+                  ancestor->Tag() == nsGkAtoms::foreignObject )
+    return NS_ERROR_FAILURE;
+
+  return NS_OK;
+}
+
+nsresult
+nsSVGUtils::AppendTransformUptoElement(nsIContent *aContent, nsIDOMSVGElement *aElement, nsIDOMSVGMatrix * *aCTM){
+  nsresult rv;
+  nsCOMPtr<nsIDOMSVGElement> element = do_QueryInterface(aContent);
+  nsIContent *ancestor = GetParentElement(aContent);
+  if (!aElement) {
+    // calculating GetScreenCTM(): traverse upto the root or non-SVG content.
+    if (ancestor && ancestor->GetNameSpaceID() == kNameSpaceID_SVG) {
+      if (ancestor->Tag() == nsGkAtoms::foreignObject && aContent->Tag() != nsGkAtoms::svg)
+        return NS_ERROR_FAILURE;
+      rv = AppendTransformUptoElement(ancestor, aElement, aCTM);
+      if (NS_FAILED(rv)) return rv;
+    }
+  } else if (element != aElement) { // calculating GetCTM(): stop at aElement.
+    NS_ASSERTION(ancestor != nsnull, "ancestor shouldn't be null.");
+    if (!ancestor)
+      return NS_ERROR_FAILURE;
+    rv = AppendTransformUptoElement(ancestor, aElement, aCTM);
+    if (NS_FAILED(rv)) return rv;
+  }
+
+  nsCOMPtr<nsIDOMSVGMatrix> tmp;
+  if (nsCOMPtr<nsIDOMSVGSVGElement>(do_QueryInterface(aContent))) {
+    nsSVGSVGElement *svgElement = static_cast<nsSVGSVGElement*>(aContent);
+    rv = svgElement->AppendTransform(*aCTM, getter_AddRefs(tmp));
+    if (NS_FAILED(rv)) return rv;
+  } else if (nsCOMPtr<nsIDOMSVGTransformable>(do_QueryInterface(aContent))) {
+    nsSVGGraphicElement *graphicElement = static_cast<nsSVGGraphicElement*>(aContent);
+    rv = graphicElement->AppendTransform(*aCTM, getter_AddRefs(tmp));
+    if (NS_FAILED(rv)) return rv;
+  } else {
+    //XXX aContent may be other type of viewport-establising elements
+    //    (e.g. <use> and <symbol>) and handle them?
+  }
+  if (tmp)
+    tmp.swap(*aCTM);
 
   return NS_OK;
 }
 
+nsresult
+nsSVGUtils::GetCTM(nsIContent *aContent, nsIDOMSVGMatrix * *aCTM)
+{
+  nsresult rv;
+  nsIDocument* currentDoc = aContent->GetCurrentDoc();
+  if (currentDoc) {
+    // Flush all pending notifications so that our frames are uptodate
+    currentDoc->FlushPendingNotifications(Flush_Layout);
+  }
+
+  *aCTM = nsnull;
+  nsCOMPtr<nsIDOMSVGElement> nearestViewportElement;
+  rv = GetNearestViewportElement(aContent, getter_AddRefs(nearestViewportElement));
+  // According to the spec(http://www.w3.org/TR/SVG11/types.html#InterfaceSVGLocatable),
+  // GetCTM is strictly defined to be the CTM for nearestViewportElement,
+  // Thus, if it is null, this is null, too.
+  if (NS_FAILED(rv) || !nearestViewportElement)
+    return NS_OK; // we can't throw exceptions from this API.
+
+  nsCOMPtr<nsIDOMSVGMatrix> tmp;
+  rv = NS_NewSVGMatrix(getter_AddRefs(tmp), 1, 0, 0, 1, 0, 0);
+  if (NS_FAILED(rv)) return NS_OK; // we can't throw exceptions from this API.
+  tmp.swap(*aCTM);
+  rv = AppendTransformUptoElement(aContent, nearestViewportElement, aCTM);
+  if (NS_FAILED(rv))
+    tmp.swap(*aCTM);
+  return NS_OK; // we can't throw exceptions from this API.
+}
+
+nsresult
+nsSVGUtils::GetScreenCTM(nsIContent *aContent, nsIDOMSVGMatrix * *aCTM)
+{
+  nsresult rv;
+  nsIDocument* currentDoc = aContent->GetCurrentDoc();
+  if (currentDoc) {
+    // Flush all pending notifications so that our frames are uptodate
+    currentDoc->FlushPendingNotifications(Flush_Layout);
+  }
+
+  *aCTM = nsnull;
+
+  nsCOMPtr<nsIDOMSVGMatrix> tmp;
+  rv = NS_NewSVGMatrix(getter_AddRefs(tmp), 1, 0, 0, 1, 0, 0);
+  if (NS_FAILED(rv)) return NS_OK; // we can't throw exceptions from this API.
+  tmp.swap(*aCTM);
+  rv = AppendTransformUptoElement(aContent, nsnull, aCTM);
+  if (NS_FAILED(rv))
+    tmp.swap(*aCTM);
+  return NS_OK; // we can't throw exceptions from this API.
+}
+
 nsSVGDisplayContainerFrame*
 nsSVGUtils::GetNearestSVGViewport(nsIFrame *aFrame)
 {
   NS_ASSERTION(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected");
   if (aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) {
     return nsnull;
   }
   while ((aFrame = aFrame->GetParent())) {
--- a/layout/svg/base/src/nsSVGUtils.h
+++ b/layout/svg/base/src/nsSVGUtils.h
@@ -245,16 +245,38 @@ public:
   /*
    * Converts a nsStyleCoord into a userspace value.  Handles units
    * Factor (straight userspace), Coord (dimensioned), and Percent (of
    * the current SVG viewport)
    */
   static float CoordToFloat(nsPresContext *aPresContext,
                             nsSVGElement *aContent,
                             const nsStyleCoord &aCoord);
+
+  /*
+   * This does the actual job for GetCTM and GetScreenCTM. When called,
+   * this goes up the tree starting from aContent, until reaching to aElement.
+   * When aElement is null, this goes up to the outermost SVG parent. Then,
+   * this post-multiplies aCTM by each parent node's transformation matrix,
+   * going down the tree from aElement to aContent.
+   * This doesn't initialize aCTM. So callers usually should pass
+   * the identity matrix by aCTM.
+   */
+  static nsresult AppendTransformUptoElement(nsIContent *aContent,
+                                             nsIDOMSVGElement *aElement,
+                                             nsIDOMSVGMatrix * *aCTM);
+  /*
+   * Return the CTM
+   */
+  static nsresult GetCTM(nsIContent *aContent, nsIDOMSVGMatrix * *aCTM);
+
+  /*
+   * Return the screen CTM
+   */
+  static nsresult GetScreenCTM(nsIContent *aContent, nsIDOMSVGMatrix * *aCTM);
   /*
    * Return the nearest viewport element
    */
   static nsresult GetNearestViewportElement(nsIContent *aContent,
                                             nsIDOMSVGElement * *aNearestViewportElement);
 
   /**
    * Gets the nearest nsSVGInnerSVGFrame or nsSVGOuterSVGFrame frame. aFrame