Bug 27183 - REGRESSION: document.documentElement.getScreenCTM() returns incorrect matrix.
Summary: REGRESSION: document.documentElement.getScreenCTM() returns incorrect matrix.
Alias: None
Product: WebKit
Classification: Unclassified
Component: SVG (show other bugs)
Version: 528+ (Nightly build)
Hardware: Mac OS X 10.6
: P2 Major
Assignee: Nikolas Zimmermann
URL: http://www.w3.org/TR/SVG/types.html#I...
Depends on:
Reported: 2009-07-11 15:11 PDT by Glenn Brauen
Modified: 2010-04-01 01:30 PDT (History)
10 users (show)

See Also:

SVG test document. (2.06 KB, image/svg+xml)
2009-07-11 15:11 PDT, Glenn Brauen
no flags Details
SVG file showing workaround (2.50 KB, image/svg+xml)
2009-09-23 11:29 PDT, Glenn Brauen
no flags Details
SVG File to Dump Computed CTMs to console. (1.53 KB, image/svg+xml)
2009-09-23 12:10 PDT, Glenn Brauen
no flags Details
Initial patch (32.01 KB, patch)
2010-03-31 09:11 PDT, Nikolas Zimmermann
no flags Details | Formatted Diff | Diff
Updated patch (31.94 KB, patch)
2010-03-31 09:36 PDT, Nikolas Zimmermann
krit: review+
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Glenn Brauen 2009-07-11 15:11:49 PDT
Created attachment 32619 [details]
SVG test document.

The following javascript should convert the clientX/Y coordinates for a mouse event to SVG coordinate space:

var m = document.documentElement.getScreenCTM();
var p = document.documentElement.createSVGPoint(); 
p.x = evt.clientX;
p.y = evt.clientY; 
p = p.matrixTransform(m.inverse());

In Safari 4.0.2 and all webkit nightlies since r43284 (May 6, 2009) this coordinate transformation does not work properly.  The transformation depends on how the SVG viewport fits in the browser window.  If the SVG viewport exactly matches, the transformation seems to be alright but if you stretch the browser window horizontally or vertically, the transformation is off.

I'll attach a SVG document that demonstrates the problem by displaying a tooltip showing the SVG coordinates for mouse events when the cursor is positioned over a rectangle in the document.

What works, what doesn't:

Defect appeared in latest Safari 4 (4.0.2).  Safari 4.0.1 was fine.
Firefox 3 is fine.

Also tested webkit nightlies:
r45754 (July 11, 2009): broken
r45423 (July 1, 2009): broken
r44341 (June 4, 2009): broken
r43796 (May 16, 2009): broken
r43436 (May 9, 2009): broken
r43284 (May 6, 2009): broken
r43163 (May 3, 2009): fine
r43110 (May 1, 2009): fine

To reporoduce: 

Download and open the attached SVG (ScreenCoordinates.svg).  Mouseover the rectangle and check the position and content of the tooltip displayed - it should be located on (above and to the right of) the cursor.
Comment 1 Glenn Brauen 2009-07-17 14:19:30 PDT
Bumped to Major.  This really breaks any web app that wants to place things in SVG based on mouse events (tooltips are an obvious case).  I've also seen HTML divs positioned absolutely based on the location of SVG objects - and this is now broken in Safari 4.0.2 as well.  I suspect that is the inverse computation from what this bug describes.
Comment 2 Glenn Brauen 2009-08-13 08:14:41 PDT
Still broken in Safari 4.0.3.
Comment 3 Glenn Brauen 2009-08-27 14:12:16 PDT
Problem exists on PowerPC and Intel Mac platforms and on both OS X 10.4 and OS X 10.5.
Comment 4 Amos Hayes 2009-09-17 11:25:52 PDT
I have the same problem on Safari Version 4.0.3 (6531.9) on OS X 10.6.

Could someone with CANCONFIRM please take a look at this?
Comment 5 Glenn Brauen 2009-09-23 11:29:57 PDT
Created attachment 40007 [details]
SVG file showing workaround

I have attached an SVG file showing a workaround to this bug.  The difference is shown below and can be summarized as follows:

- The Coordinate Transformation Matrix (CTM) returned by document.documentElement.getScreenCTM() is wrong.
- Separately applying document.documentElement.currentTranslate, document.documentElement.currentScal, and a CTM computed from the event target to the SVG root node produces the correct transformation of the event clientX/clientY coordinates.

diff ScreenCoordinates.svg ScreenCoordinates_workaround.svg
>       function computeCumulativeCTMFromRoot(el, svgNode) {
>               var ctm = el.getCTM();
>               while ((el = el.parentNode) != svgNode) {
>                       ctm = el.getCTM().multiply(ctm);
>               }
>               return(ctm);
>       }
<                * Apply screen CTM transformation to the evt screenX and screenY to convert the event
>                * Apply screen translate, scale and a CTM transformation
>                * computed from the event target up to the enclosing SVG node to convert the event
<               var m = document.documentElement.getScreenCTM();
>               var trans = evt.target.ownerDocument.documentElement.currentTranslate;
>               var scale = evt.target.ownerDocument.documentElement.currentScale;
>               var m = computeCumulativeCTMFromRoot(evt.target, document.getElementById("main"));
<               p.x = evt.clientX;
<               p.y = evt.clientY; 
>               p.x = (evt.clientX - trans.x) / scale;
>               p.y = (evt.clientY - trans.y) / scale;
Comment 6 Glenn Brauen 2009-09-23 12:10:41 PDT
Created attachment 40010 [details]
SVG File to Dump Computed CTMs to console.

I have added an attachment that dumps the computed CTMs to the console log for comparison.  One of the translate values (x or y) in the CTM returned by getScreenCTM() appear to always be double the value returned by the other CTM computation.
Comment 7 Glenn Brauen 2009-09-23 12:16:41 PDT
I'm surprised this is still sitting as unconfirmed.  This happens in all versions of Safari since 4.0.2 (in fact since nightly build r43284 - May 6, 2009 as I point out above) on PowerPC and Intel Macs running any of Mac OS 10.4, 10.5 or 10.6.

Maybe everybody has been computing their own CTMs all along...
Comment 8 Dirk Schulze 2009-12-06 10:54:47 PST
getCTM and getScreenCTM are specified by SVGLocatable. Both do the same. While getCTM should return the CTM according to the viewport coordinate system, getScreenCTM should return CTM according to the parent user agent. getScreenCTM is wrong in trunk. See http://www.w3.org/TR/SVG/types.html#InterfaceSVGLocatable .
Comment 9 Andreas Neumann 2010-03-12 04:53:54 PST

I also think that this bug should be fixed as soon as possible, as this method is very essential to many interactive SVG applications. Many other javascript functions build upon the SVGLocatable interface.

It is also a regression of something that previously worked.

Related to this bug are:

Here is another good testcase:
If the browser window is very wide then the red circles are way off the mouse cursor, while they should appear directly below the cursor.

BTW: Glenn, thanks for posting the workaround!
Comment 10 Eric Seidel (no email) 2010-03-23 23:13:33 PDT
With a reduced test case, someone should be able to run bisect-builds over the nightlies:
and find out what revision range caused this regression.  Then we can better understand what went wrong and who might know best how to fix it. :)
Comment 11 Nikolas Zimmermann 2010-03-24 04:38:03 PDT
Running "bisect-builds -r 43163:43284" - Glenn thanks a lot for the detailed investigation!
Comment 12 Nikolas Zimmermann 2010-03-25 08:49:49 PDT
All issues with getScreenCTM fixed, going to upload a patch soon, stay tuned...
Comment 13 Nikolas Zimmermann 2010-03-31 09:11:28 PDT
Created attachment 52178 [details]
Initial patch

Phew, this was more tricky than I thought. This is only a regression, because of pure luck. The getScreenCTM() implementation was just plain wrong.
When we added support to handle non-SVG parent elements (mixed content documents, ie. a <div> containing a <svg>) the implementation of getScreenCTM() got worse. It has never been correct so far, and finally is after this patch.

Checked all testcases against Opera / Firefox, which both have bugs.
Firefox mixes up getCTM/getScreenCTM: when using absolute positioned <div> object containing a <svg> it lists the left/top CSS translation as translation values in the getCTM matrix, instead of just in the getScreenCTM matrix. Opera gets all of this right. Opera has another bug leading to null matrices in getCTM() when using viewBox on a <svg> fragment in a XHTML document.

I double-checked all testcases, by hand (fun to multiply this by hand and verify ;-) and I'm happy with it now.
Comment 14 Early Warning System Bot 2010-03-31 09:20:21 PDT
Attachment 52178 [details] did not build on qt:
Build output: http://webkit-commit-queue.appspot.com/results/1610093
Comment 15 WebKit Review Bot 2010-03-31 09:21:12 PDT
Attachment 52178 [details] did not build on chromium:
Build output: http://webkit-commit-queue.appspot.com/results/1591108
Comment 16 Nikolas Zimmermann 2010-03-31 09:36:14 PDT
Created attachment 52180 [details]
Updated patch

s/DocumentScope/ScreenScope/ upon Dirks request.
Remove CString.h include (debugging leftover) - EWS complained because if has recently been moved to wtf/CString.h
Fix typo s/substract/subtract/

No substantial changes.
Comment 17 Dirk Schulze 2010-03-31 09:39:11 PDT
Comment on attachment 52180 [details]
Updated patch

Comment 18 Nikolas Zimmermann 2010-04-01 01:30:51 PDT
Committed r56905: <http://trac.webkit.org/changeset/56905>