Incorrect position when dragging jQuery Draggable elements with position fixed after...
authorsimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 12 May 2017 23:48:12 +0000 (23:48 +0000)
committersimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 12 May 2017 23:48:12 +0000 (23:48 +0000)
https://bugs.webkit.org/show_bug.cgi?id=171113
Source/WebCore:

rdar://problem/31746516

Reviewed by Tim Horton.

Make getBoundingClientRect() and getClientRects() return rects which are relative to the layout
viewport, rather than the visual viewport. This goes part of the way to fixing webkit.org/b/170981,
which aims to make pinch-zoom invisible to web pages ("inert visual viewport"). It fixes issues on various
sites like Facebook when zoomed.

Factor coordinate conversion code into functions on FrameView, which now documents
the various coordinate systems in a big comment. Document::adjustFloatQuadsForScrollAndAbsoluteZoomAndFrameScale()
and Document::adjustFloatRectForScrollAndAbsoluteZoomAndFrameScale() are renamed and factored
to use these helpers.

There are two behavior changes here:

1. FrameView::documentToClientOffset() now uses the origin of the layout viewport in the "document to client"
   coordinate mapping.

2. The two document functions would apply the scale and offset in the wrong order. We need
   to first undo the effects of CSS zoom, page zoom and page scale, and then map from document
   to client coordinates.

Tests: fast/visual-viewport/client-rects-relative-to-layout-viewport.html
       fast/zooming/client-rects-with-css-and-page-zoom.html

* dom/Document.cpp:
(WebCore::Document::convertAbsoluteToClientQuads):
(WebCore::Document::convertAbsoluteToClientRect):
(WebCore::Document::adjustFloatQuadsForScrollAndAbsoluteZoomAndFrameScale): Deleted.
(WebCore::Document::adjustFloatRectForScrollAndAbsoluteZoomAndFrameScale): Deleted.
* dom/Document.h:
* dom/Element.cpp:
(WebCore::Element::getClientRects):
(WebCore::Element::getBoundingClientRect):
* dom/Range.cpp:
(WebCore::Range::borderAndTextQuads):
* page/FrameView.cpp:
(WebCore::FrameView::absoluteToDocumentScaleFactor):
(WebCore::FrameView::absoluteToDocumentRect):
(WebCore::FrameView::absoluteToDocumentPoint):
(WebCore::FrameView::documentToClientOffset):
(WebCore::FrameView::documentToClientRect):
(WebCore::FrameView::documentToClientPoint):
* page/FrameView.h:
* platform/ScrollableArea.h: #pragma once
* platform/Scrollbar.h: #pragma once
* platform/Widget.h: #pragma once

LayoutTests:

Reviewed by Tim Horton.

Rebaseline tests which dumped the getBoundingClientRect for fixed elements; now that these
are layout viewport-relative, getBoundingClientRect() for a fixed element is unchanging.

New test that exercises getBoundingClientRect() and getClientRects() for fixed and absolute
elements after zooming.

* fast/events/autoscroll-when-zoomed.html: Adjust to account for behavior change.
* fast/visual-viewport/client-rects-relative-to-layout-viewport-expected.txt: Added.
* fast/visual-viewport/client-rects-relative-to-layout-viewport.html: Added.
* fast/visual-viewport/zoomed-fixed-expected.txt:
* fast/visual-viewport/zoomed-fixed-header-and-footer-expected.txt:
* fast/zooming/client-rect-in-fixed-zoomed-expected.txt:
* fast/zooming/client-rect-in-fixed-zoomed.html:
* fast/zooming/client-rects-with-css-and-page-zoom-expected.txt: Added.
* fast/zooming/client-rects-with-css-and-page-zoom.html: Added.
* platform/ios-wk2/fast/visual-viewport/client-rects-relative-to-layout-viewport-expected.txt: Added.
* platform/ios/TestExpectations: Mark imported/w3c/web-platform-tests/cssom-view/elementFromPoint.html as failing;
    it will be fixed via webkit.org/b/172019
* platform/ios/fast/visual-viewport/zoomed-fixed-expected.txt:
* platform/ios/fast/visual-viewport/zoomed-fixed-header-and-footer-expected.txt:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@216803 268f45cc-cd09-0410-ab3c-d52691b4dbfc

24 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/events/autoscroll-when-zoomed.html
LayoutTests/fast/visual-viewport/client-rects-relative-to-layout-viewport-expected.txt [new file with mode: 0644]
LayoutTests/fast/visual-viewport/client-rects-relative-to-layout-viewport.html [new file with mode: 0644]
LayoutTests/fast/visual-viewport/zoomed-fixed-expected.txt
LayoutTests/fast/visual-viewport/zoomed-fixed-header-and-footer-expected.txt
LayoutTests/fast/zooming/client-rect-in-fixed-zoomed-expected.txt
LayoutTests/fast/zooming/client-rect-in-fixed-zoomed.html
LayoutTests/fast/zooming/client-rects-with-css-and-page-zoom-expected.txt [new file with mode: 0644]
LayoutTests/fast/zooming/client-rects-with-css-and-page-zoom.html [new file with mode: 0644]
LayoutTests/platform/ios-wk2/fast/visual-viewport/client-rects-relative-to-layout-viewport-expected.txt [new file with mode: 0644]
LayoutTests/platform/ios/TestExpectations
LayoutTests/platform/ios/fast/visual-viewport/zoomed-fixed-expected.txt
LayoutTests/platform/ios/fast/visual-viewport/zoomed-fixed-header-and-footer-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Document.h
Source/WebCore/dom/Element.cpp
Source/WebCore/dom/Range.cpp
Source/WebCore/page/FrameView.cpp
Source/WebCore/page/FrameView.h
Source/WebCore/platform/ScrollableArea.h
Source/WebCore/platform/Scrollbar.h
Source/WebCore/platform/Widget.h

index e964af6..e2b9878 100644 (file)
@@ -1,3 +1,31 @@
+2017-05-11  Simon Fraser  <simon.fraser@apple.com>
+
+        Incorrect position when dragging jQuery Draggable elements with position fixed after pinch zoom
+        https://bugs.webkit.org/show_bug.cgi?id=171113
+
+        Reviewed by Tim Horton.
+
+        Rebaseline tests which dumped the getBoundingClientRect for fixed elements; now that these
+        are layout viewport-relative, getBoundingClientRect() for a fixed element is unchanging.
+
+        New test that exercises getBoundingClientRect() and getClientRects() for fixed and absolute
+        elements after zooming.
+
+        * fast/events/autoscroll-when-zoomed.html: Adjust to account for behavior change.
+        * fast/visual-viewport/client-rects-relative-to-layout-viewport-expected.txt: Added.
+        * fast/visual-viewport/client-rects-relative-to-layout-viewport.html: Added.
+        * fast/visual-viewport/zoomed-fixed-expected.txt:
+        * fast/visual-viewport/zoomed-fixed-header-and-footer-expected.txt:
+        * fast/zooming/client-rect-in-fixed-zoomed-expected.txt:
+        * fast/zooming/client-rect-in-fixed-zoomed.html:
+        * fast/zooming/client-rects-with-css-and-page-zoom-expected.txt: Added.
+        * fast/zooming/client-rects-with-css-and-page-zoom.html: Added.
+        * platform/ios-wk2/fast/visual-viewport/client-rects-relative-to-layout-viewport-expected.txt: Added.
+        * platform/ios/TestExpectations: Mark imported/w3c/web-platform-tests/cssom-view/elementFromPoint.html as failing;
+            it will be fixed via webkit.org/b/172019
+        * platform/ios/fast/visual-viewport/zoomed-fixed-expected.txt:
+        * platform/ios/fast/visual-viewport/zoomed-fixed-header-and-footer-expected.txt:
+
 2017-05-12  Mark Lam  <mark.lam@apple.com>
 
         WorkerRunLoop::Task::performTask() should check !scriptController->isTerminatingExecution().
index da5a835..b789004 100644 (file)
             var lineHeight = containerRect.height / 3;
             
             window.scrollTo(0, containerRect.bottom - lineHeight);
-
             containerRect = document.getElementById('container').getBoundingClientRect(); // Scrolling changed it.
 
-            start = { x: containerRect.left + 10, y: containerRect.bottom - lineHeight / 3 };
+            start = { x: containerRect.left + 10, y: 2 * lineHeight / 3 };
             end = { x: containerRect.right - 10, y: 16 };
             
             eventSender.mouseMoveTo(pageScale * start.x, pageScale * start.y);
diff --git a/LayoutTests/fast/visual-viewport/client-rects-relative-to-layout-viewport-expected.txt b/LayoutTests/fast/visual-viewport/client-rects-relative-to-layout-viewport-expected.txt
new file mode 100644 (file)
index 0000000..24afb22
--- /dev/null
@@ -0,0 +1,39 @@
+This test zooms and scrolls the page and checks that client rects are correct.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+layoutViewport: 0, 0 - 785 x 585
+visualViewportRect: 0, 0 - 392.5 x 292.5
+fixed client bounds: 12, 10 - 30 x 20
+fixed client rect: 12, 10 - 30 x 20
+absolute client bounds: 120, 100 - 50 x 25
+absolute client rect: 120, 100 - 50 x 25
+
+Scrolled to 476, 526
+layoutViewport: 83.5, 233.5 - 785 x 585
+visualViewportRect: 476, 526 - 392.5 x 292.5
+fixed client bounds: 12, 10 - 30 x 20
+fixed client rect: 12, 10 - 30 x 20
+absolute client bounds: 36.5, -133.5 - 50 x 25
+absolute client rect: 36, -134 - 51 x 26
+
+Scrolled to 100, 776
+layoutViewport: 83.5, 483.5 - 785 x 585
+visualViewportRect: 100, 776 - 392.5 x 292.5
+fixed client bounds: 12, 10 - 30 x 20
+fixed client rect: 12, 10 - 30 x 20
+absolute client bounds: 36.5, -383.5 - 50 x 25
+absolute client rect: 36, -384 - 51 x 26
+
+Scrolled to 50, 300
+layoutViewport: 50, 300 - 785 x 585
+visualViewportRect: 50, 300 - 392.5 x 292.5
+fixed client bounds: 12, 10 - 30 x 20
+fixed client rect: 12, 10 - 30 x 20
+absolute client bounds: 70, -200 - 50 x 25
+absolute client rect: 70, -200 - 50 x 25
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/visual-viewport/client-rects-relative-to-layout-viewport.html b/LayoutTests/fast/visual-viewport/client-rects-relative-to-layout-viewport.html
new file mode 100644 (file)
index 0000000..6ad520a
--- /dev/null
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+    <style>
+        body {
+            height: 3000px;
+            width: 2000px;
+        }
+        #fixed {
+            position: fixed;
+            top: 10px;
+            left: 12px;
+            width: 30px;
+            height: 20px;
+            background-color: silver;
+        }
+        #absolute {
+            position: absolute;
+            top: 100px;
+            left: 120px;
+            width: 50px;
+            height: 25px;
+            background-color: blue;
+        }
+    </style>
+    <script src="../../resources/js-test-pre.js"></script>
+    <script>
+        description("This test zooms and scrolls the page and checks that client rects are correct.");
+
+        if (window.internals)
+            internals.settings.setVisualViewportEnabled(true);
+
+        window.jsTestIsAsync = true;
+
+        function stringFromRect(domRect)
+        {
+            return `${domRect.x}, ${domRect.y} - ${domRect.width} x ${domRect.height}`;
+        }
+
+        function dumpRects()
+        {
+            var fixed = document.getElementById('fixed');
+            var absolute = document.getElementById('absolute');
+
+            debug('layoutViewport: ' + stringFromRect(internals.layoutViewportRect()));
+            debug('visualViewportRect: ' + stringFromRect(internals.visualViewportRect()));
+
+            debug('fixed client bounds: ' + stringFromRect(fixed.getBoundingClientRect()));
+            debug('fixed client rect: ' + stringFromRect(fixed.getClientRects()[0]));
+
+            debug('absolute client bounds: ' + stringFromRect(absolute.getBoundingClientRect()));
+            debug('absolute client rect: ' + stringFromRect(absolute.getClientRects()[0]));
+        }
+
+        function doAfterZooming()
+        {
+            window.scrollTo(0, 0);
+            dumpRects();
+
+            debug('');
+            window.scrollTo(476, 526);
+            debug('Scrolled to ' + window.scrollX + ', ' + window.scrollY);
+            dumpRects();
+
+            debug('');
+            window.scrollTo(100, 776);
+            debug('Scrolled to ' + window.scrollX + ', ' + window.scrollY);
+            dumpRects();
+
+            debug('');
+            window.scrollTo(50, 300);
+            debug('Scrolled to ' + window.scrollX + ', ' + window.scrollY);
+            dumpRects();
+
+            window.scrollTo(0, 0);
+
+            finishJSTest();
+        }
+        
+        function getUIScript()
+        {
+            return `(function() {
+                uiController.zoomToScale(2, function() {
+                    uiController.uiScriptComplete(uiController.zoomScale);
+                });
+            })();`;
+        }
+
+        function doTest()
+        {
+            if (!window.testRunner)
+                return;
+
+            testRunner.runUIScript(getUIScript(), function(zoomScale) {
+                doAfterZooming();
+            });
+        }
+        
+        window.addEventListener('load', doTest, false);
+    </script>
+</head>
+<body>
+    <div id="fixed"></div>
+    <div id="absolute"></div>
+    <script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
index 38cf0f7..5b3ac20 100644 (file)
@@ -18,25 +18,25 @@ Scrolled to 475, 525
 JSON.stringify(internals.layoutViewportRect()) is {"x":82.5,"y":232.5,"width":785,"height":585,"top":232.5,"right":867.5,"bottom":817.5,"left":82.5}
 JSON.stringify(internals.visualViewportRect()) is {"x":475,"y":525,"width":392.5,"height":292.5,"top":525,"right":867.5,"bottom":817.5,"left":475}
 client rect of top:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-392.5,"y":-292.5,"width":785,"height":100,"top":-292.5,"right":392.5,"bottom":-192.5,"left":-392.5}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":785,"height":100,"top":0,"right":785,"bottom":100,"left":0}
 client rect of bottom:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-392.5,"y":192.5,"width":785,"height":100,"top":192.5,"right":392.5,"bottom":292.5,"left":-392.5}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":485,"width":785,"height":100,"top":485,"right":785,"bottom":585,"left":0}
 client rect of left:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-392.5,"y":-292.5,"width":100,"height":585,"top":-292.5,"right":-292.5,"bottom":292.5,"left":-392.5}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":100,"height":585,"top":0,"right":100,"bottom":585,"left":0}
 client rect of right:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":292.5,"y":-292.5,"width":100,"height":585,"top":-292.5,"right":392.5,"bottom":292.5,"left":292.5}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":685,"y":0,"width":100,"height":585,"top":0,"right":785,"bottom":585,"left":685}
 
 Scrolled to 100, 776
 JSON.stringify(internals.layoutViewportRect()) is {"x":82.5,"y":483.5,"width":785,"height":585,"top":483.5,"right":867.5,"bottom":1068.5,"left":82.5}
 JSON.stringify(internals.visualViewportRect()) is {"x":100,"y":776,"width":392.5,"height":292.5,"top":776,"right":492.5,"bottom":1068.5,"left":100}
 client rect of top:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-17.5,"y":-292.5,"width":785,"height":100,"top":-292.5,"right":767.5,"bottom":-192.5,"left":-17.5}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":785,"height":100,"top":0,"right":785,"bottom":100,"left":0}
 client rect of bottom:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-17.5,"y":192.5,"width":785,"height":100,"top":192.5,"right":767.5,"bottom":292.5,"left":-17.5}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":485,"width":785,"height":100,"top":485,"right":785,"bottom":585,"left":0}
 client rect of left:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-17.5,"y":-292.5,"width":100,"height":585,"top":-292.5,"right":82.5,"bottom":292.5,"left":-17.5}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":100,"height":585,"top":0,"right":100,"bottom":585,"left":0}
 client rect of right:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":667.5,"y":-292.5,"width":100,"height":585,"top":-292.5,"right":767.5,"bottom":292.5,"left":667.5}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":685,"y":0,"width":100,"height":585,"top":0,"right":785,"bottom":585,"left":685}
 
 Scrolled to 50, 300
 JSON.stringify(internals.layoutViewportRect()) is {"x":50,"y":300,"width":785,"height":585,"top":300,"right":835,"bottom":885,"left":50}
index 0c85851..98f94c3 100644 (file)
@@ -18,37 +18,37 @@ Scrolled to 475, 525
 JSON.stringify(internals.layoutViewportRect()) is {"x":82.5,"y":211,"width":785,"height":585,"top":211,"right":867.5,"bottom":796,"left":82.5}
 JSON.stringify(internals.visualViewportRect()) is {"x":475,"y":503.5,"width":392.5,"height":292.5,"top":503.5,"right":867.5,"bottom":796,"left":475}
 client rect of top:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-392.5,"y":-314,"width":785,"height":100,"top":-314,"right":392.5,"bottom":-214,"left":-392.5}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":785,"height":100,"top":0,"right":785,"bottom":100,"left":0}
 client rect of bottom:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-392.5,"y":171,"width":785,"height":100,"top":171,"right":392.5,"bottom":271,"left":-392.5}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":485,"width":785,"height":100,"top":485,"right":785,"bottom":585,"left":0}
 client rect of left:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-392.5,"y":-314,"width":100,"height":585,"top":-314,"right":-292.5,"bottom":271,"left":-392.5}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":100,"height":585,"top":0,"right":100,"bottom":585,"left":0}
 client rect of right:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":292.5,"y":-314,"width":100,"height":585,"top":-314,"right":392.5,"bottom":271,"left":292.5}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":685,"y":0,"width":100,"height":585,"top":0,"right":785,"bottom":585,"left":685}
 
 Scrolled to 100, 776
 JSON.stringify(internals.layoutViewportRect()) is {"x":82.5,"y":462,"width":785,"height":585,"top":462,"right":867.5,"bottom":1047,"left":82.5}
 JSON.stringify(internals.visualViewportRect()) is {"x":100,"y":754.5,"width":392.5,"height":292.5,"top":754.5,"right":492.5,"bottom":1047,"left":100}
 client rect of top:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-17.5,"y":-314,"width":785,"height":100,"top":-314,"right":767.5,"bottom":-214,"left":-17.5}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":785,"height":100,"top":0,"right":785,"bottom":100,"left":0}
 client rect of bottom:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-17.5,"y":171,"width":785,"height":100,"top":171,"right":767.5,"bottom":271,"left":-17.5}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":485,"width":785,"height":100,"top":485,"right":785,"bottom":585,"left":0}
 client rect of left:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-17.5,"y":-314,"width":100,"height":585,"top":-314,"right":82.5,"bottom":271,"left":-17.5}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":100,"height":585,"top":0,"right":100,"bottom":585,"left":0}
 client rect of right:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":667.5,"y":-314,"width":100,"height":585,"top":-314,"right":767.5,"bottom":271,"left":667.5}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":685,"y":0,"width":100,"height":585,"top":0,"right":785,"bottom":585,"left":685}
 
 Scrolled to 50, 300
 JSON.stringify(internals.layoutViewportRect()) is {"x":50,"y":278.5,"width":785,"height":585,"top":278.5,"right":835,"bottom":863.5,"left":50}
 JSON.stringify(internals.visualViewportRect()) is {"x":50,"y":278.5,"width":392.5,"height":292.5,"top":278.5,"right":442.5,"bottom":571,"left":50}
 client rect of top:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":-21.5,"width":785,"height":100,"top":-21.5,"right":785,"bottom":78.5,"left":0}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":785,"height":100,"top":0,"right":785,"bottom":100,"left":0}
 client rect of bottom:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":463.5,"width":785,"height":100,"top":463.5,"right":785,"bottom":563.5,"left":0}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":485,"width":785,"height":100,"top":485,"right":785,"bottom":585,"left":0}
 client rect of left:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":-21.5,"width":100,"height":585,"top":-21.5,"right":100,"bottom":563.5,"left":0}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":100,"height":585,"top":0,"right":100,"bottom":585,"left":0}
 client rect of right:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":685,"y":-21.5,"width":100,"height":585,"top":-21.5,"right":785,"bottom":563.5,"left":685}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":685,"y":0,"width":100,"height":585,"top":0,"right":785,"bottom":585,"left":685}
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 3f057d6..774c015 100644 (file)
@@ -4,7 +4,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
 
 
 PASS internals.pageScaleFactor() is 2
-PASS JSON.stringify(clientRect) is JSON.stringify({x: 22, y: -108, width: 20, height: 10, top: -108, right: 42, bottom: -98, left: 22})
+PASS JSON.stringify(clientRect) is JSON.stringify({x: 22, y: 32, width: 20, height: 10, top: 32, right: 42, bottom:42, left: 22})
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 15a1e25..31cf770 100644 (file)
@@ -40,7 +40,7 @@
             var box = document.getElementById('box');
             clientRect = box.getBoundingClientRect();
             
-            shouldBe('JSON.stringify(clientRect)', 'JSON.stringify({x: 22, y: -108, width: 20, height: 10, top: -108, right: 42, bottom: -98, left: 22})');
+            shouldBe('JSON.stringify(clientRect)', 'JSON.stringify({x: 22, y: 32, width: 20, height: 10, top: 32, right: 42, bottom:42, left: 22})');
             
             finishJSTest();
         }
diff --git a/LayoutTests/fast/zooming/client-rects-with-css-and-page-zoom-expected.txt b/LayoutTests/fast/zooming/client-rects-with-css-and-page-zoom-expected.txt
new file mode 100644 (file)
index 0000000..463001f
--- /dev/null
@@ -0,0 +1,21 @@
+Tests getBoundingClient rect with both CSS zoom and page zoom
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+Testing box with CSS zoom on an ancestor
+PASS bounds.left.toFixed(2) is "18.00"
+PASS bounds.top.toFixed(2) is "150.00"
+PASS bounds.width.toFixed(2) is "40.00"
+PASS bounds.height.toFixed(2) is "50.00"
+
+Testing box without CSS zoom on an ancestor
+PASS bounds.left.toFixed(2) is "21.00"
+PASS bounds.top.toFixed(2) is "150.00"
+PASS bounds.width.toFixed(2) is "40.00"
+PASS bounds.height.toFixed(2) is "50.00"
+
diff --git a/LayoutTests/fast/zooming/client-rects-with-css-and-page-zoom.html b/LayoutTests/fast/zooming/client-rects-with-css-and-page-zoom.html
new file mode 100644 (file)
index 0000000..e631bb5
--- /dev/null
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+    <style>
+        .container {
+            zoom: 2.3;
+        }
+
+        #inside-zoom-box {
+            position: absolute;
+            left: 18px;
+            top: 150px;
+            height: 50px;
+            width: 40px;
+            background-color: rgba(0, 0, 0, 0.1);
+        }
+
+        #outside-zoom-box {
+            position: absolute;
+            left: 21px;
+            top: 150px;
+            height: 50px;
+            width: 40px;
+            background-color: rgba(0, 0, 0, 0.1);
+        }
+    </style>
+    <script src="../../resources/js-test-pre.js"></script>
+    <script>
+        description("Tests getBoundingClient rect with both CSS zoom and page zoom");
+
+        var box;
+        var bounds;
+        function doTest()
+        {
+            if (window.eventSender) {
+                eventSender.zoomPageIn();
+                eventSender.zoomPageIn();
+            } else {
+                debug('This tests uses eventSender to zoom in twice');
+            }
+            
+            box = document.getElementById('inside-zoom-box');
+            bounds = box.getBoundingClientRect();
+
+            debug('');
+            debug('Testing box with CSS zoom on an ancestor')
+            shouldBeEqualToString('bounds.left.toFixed(2)', '18.00');
+            shouldBeEqualToString('bounds.top.toFixed(2)', '150.00');
+            shouldBeEqualToString('bounds.width.toFixed(2)', '40.00');
+            shouldBeEqualToString('bounds.height.toFixed(2)', '50.00');
+
+            box = document.getElementById('outside-zoom-box');
+            bounds = box.getBoundingClientRect();
+
+            debug('');
+            debug('Testing box without CSS zoom on an ancestor');
+            shouldBeEqualToString('bounds.left.toFixed(2)', '21.00');
+            shouldBeEqualToString('bounds.top.toFixed(2)', '150.00');
+            shouldBeEqualToString('bounds.width.toFixed(2)', '40.00');
+            shouldBeEqualToString('bounds.height.toFixed(2)', '50.00');
+        }
+        
+        window.addEventListener('load', doTest, false);
+    </script>
+</head>
+<body>
+    <div class="container">
+        <div id="inside-zoom-box"></div>
+    </div>
+    <div id="outside-zoom-box"></div>
+    <script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/platform/ios-wk2/fast/visual-viewport/client-rects-relative-to-layout-viewport-expected.txt b/LayoutTests/platform/ios-wk2/fast/visual-viewport/client-rects-relative-to-layout-viewport-expected.txt
new file mode 100644 (file)
index 0000000..38e1f6a
--- /dev/null
@@ -0,0 +1,39 @@
+This test zooms and scrolls the page and checks that client rects are correct.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+layoutViewport: 0, 0 - 800 x 600
+visualViewportRect: 0, 0 - 400 x 300
+fixed client bounds: 12, 10 - 30 x 20
+fixed client rect: 12, 10 - 30 x 20
+absolute client bounds: 120, 100 - 50 x 25
+absolute client rect: 120, 100 - 50 x 25
+
+Scrolled to 476, 526
+layoutViewport: 0, 0 - 800 x 600
+visualViewportRect: 476, 526 - 400 x 300
+fixed client bounds: 12, 10 - 30 x 20
+fixed client rect: 12, 10 - 30 x 20
+absolute client bounds: 120, 100 - 50 x 25
+absolute client rect: 120, 100 - 50 x 25
+
+Scrolled to 100, 776
+layoutViewport: 0, 0 - 800 x 600
+visualViewportRect: 100, 776 - 400 x 300
+fixed client bounds: 12, 10 - 30 x 20
+fixed client rect: 12, 10 - 30 x 20
+absolute client bounds: 120, 100 - 50 x 25
+absolute client rect: 120, 100 - 50 x 25
+
+Scrolled to 50, 300
+layoutViewport: 0, 0 - 800 x 600
+visualViewportRect: 50, 300 - 400 x 300
+fixed client bounds: 12, 10 - 30 x 20
+fixed client rect: 12, 10 - 30 x 20
+absolute client bounds: 120, 100 - 50 x 25
+absolute client rect: 120, 100 - 50 x 25
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
index e72fb57..c8c094a 100644 (file)
@@ -2953,3 +2953,5 @@ webkit.org/b/124933 svg/animations/getCurrentTime-pause-unpause.html [ Pass Fail
 
 webkit.org/b/171760 imported/w3c/i18n/bidi/bidi-plaintext-011.html [ ImageOnlyFailure ]
 webkit.org/b/171760 imported/w3c/i18n/bidi/block-plaintext-005.html [ ImageOnlyFailure ]
+
+webkit.org/b/172019 imported/w3c/web-platform-tests/cssom-view/elementFromPoint.html [ Failure ]
index c669bb6..bb88099 100644 (file)
@@ -18,37 +18,37 @@ Scrolled to 475, 525
 JSON.stringify(internals.layoutViewportRect()) is {"x":0,"y":0,"width":800,"height":600,"top":0,"right":800,"bottom":600,"left":0}
 JSON.stringify(internals.visualViewportRect()) is {"x":475,"y":525,"width":400,"height":300,"top":525,"right":875,"bottom":825,"left":475}
 client rect of top:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-475,"y":-525,"width":800,"height":100,"top":-525,"right":325,"bottom":-425,"left":-475}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":800,"height":100,"top":0,"right":800,"bottom":100,"left":0}
 client rect of bottom:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-475,"y":-25,"width":800,"height":100,"top":-25,"right":325,"bottom":75,"left":-475}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":500,"width":800,"height":100,"top":500,"right":800,"bottom":600,"left":0}
 client rect of left:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-475,"y":-525,"width":100,"height":600,"top":-525,"right":-375,"bottom":75,"left":-475}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":100,"height":600,"top":0,"right":100,"bottom":600,"left":0}
 client rect of right:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":225,"y":-525,"width":100,"height":600,"top":-525,"right":325,"bottom":75,"left":225}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":700,"y":0,"width":100,"height":600,"top":0,"right":800,"bottom":600,"left":700}
 
 Scrolled to 100, 776
 JSON.stringify(internals.layoutViewportRect()) is {"x":0,"y":0,"width":800,"height":600,"top":0,"right":800,"bottom":600,"left":0}
 JSON.stringify(internals.visualViewportRect()) is {"x":100,"y":776,"width":400,"height":300,"top":776,"right":500,"bottom":1076,"left":100}
 client rect of top:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-100,"y":-776,"width":800,"height":100,"top":-776,"right":700,"bottom":-676,"left":-100}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":800,"height":100,"top":0,"right":800,"bottom":100,"left":0}
 client rect of bottom:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-100,"y":-276,"width":800,"height":100,"top":-276,"right":700,"bottom":-176,"left":-100}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":500,"width":800,"height":100,"top":500,"right":800,"bottom":600,"left":0}
 client rect of left:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-100,"y":-776,"width":100,"height":600,"top":-776,"right":0,"bottom":-176,"left":-100}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":100,"height":600,"top":0,"right":100,"bottom":600,"left":0}
 client rect of right:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":600,"y":-776,"width":100,"height":600,"top":-776,"right":700,"bottom":-176,"left":600}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":700,"y":0,"width":100,"height":600,"top":0,"right":800,"bottom":600,"left":700}
 
 Scrolled to 50, 300
 JSON.stringify(internals.layoutViewportRect()) is {"x":0,"y":0,"width":800,"height":600,"top":0,"right":800,"bottom":600,"left":0}
 JSON.stringify(internals.visualViewportRect()) is {"x":50,"y":300,"width":400,"height":300,"top":300,"right":450,"bottom":600,"left":50}
 client rect of top:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-50,"y":-300,"width":800,"height":100,"top":-300,"right":750,"bottom":-200,"left":-50}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":800,"height":100,"top":0,"right":800,"bottom":100,"left":0}
 client rect of bottom:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-50,"y":200,"width":800,"height":100,"top":200,"right":750,"bottom":300,"left":-50}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":500,"width":800,"height":100,"top":500,"right":800,"bottom":600,"left":0}
 client rect of left:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-50,"y":-300,"width":100,"height":600,"top":-300,"right":50,"bottom":300,"left":-50}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":100,"height":600,"top":0,"right":100,"bottom":600,"left":0}
 client rect of right:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":650,"y":-300,"width":100,"height":600,"top":-300,"right":750,"bottom":300,"left":650}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":700,"y":0,"width":100,"height":600,"top":0,"right":800,"bottom":600,"left":700}
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 758c9ac..63f19ed 100644 (file)
@@ -18,37 +18,37 @@ Scrolled to 475, 525
 JSON.stringify(internals.layoutViewportRect()) is {"x":0,"y":0,"width":800,"height":600,"top":0,"right":800,"bottom":600,"left":0}
 JSON.stringify(internals.visualViewportRect()) is {"x":475,"y":482,"width":400,"height":300,"top":482,"right":875,"bottom":782,"left":475}
 client rect of top:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-475,"y":-525,"width":800,"height":100,"top":-525,"right":325,"bottom":-425,"left":-475}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":800,"height":100,"top":0,"right":800,"bottom":100,"left":0}
 client rect of bottom:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-475,"y":-25,"width":800,"height":100,"top":-25,"right":325,"bottom":75,"left":-475}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":500,"width":800,"height":100,"top":500,"right":800,"bottom":600,"left":0}
 client rect of left:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-475,"y":-525,"width":100,"height":600,"top":-525,"right":-375,"bottom":75,"left":-475}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":100,"height":600,"top":0,"right":100,"bottom":600,"left":0}
 client rect of right:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":225,"y":-525,"width":100,"height":600,"top":-525,"right":325,"bottom":75,"left":225}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":700,"y":0,"width":100,"height":600,"top":0,"right":800,"bottom":600,"left":700}
 
 Scrolled to 100, 776
 JSON.stringify(internals.layoutViewportRect()) is {"x":0,"y":0,"width":800,"height":600,"top":0,"right":800,"bottom":600,"left":0}
 JSON.stringify(internals.visualViewportRect()) is {"x":100,"y":733,"width":400,"height":300,"top":733,"right":500,"bottom":1033,"left":100}
 client rect of top:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-100,"y":-776,"width":800,"height":100,"top":-776,"right":700,"bottom":-676,"left":-100}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":800,"height":100,"top":0,"right":800,"bottom":100,"left":0}
 client rect of bottom:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-100,"y":-276,"width":800,"height":100,"top":-276,"right":700,"bottom":-176,"left":-100}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":500,"width":800,"height":100,"top":500,"right":800,"bottom":600,"left":0}
 client rect of left:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-100,"y":-776,"width":100,"height":600,"top":-776,"right":0,"bottom":-176,"left":-100}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":100,"height":600,"top":0,"right":100,"bottom":600,"left":0}
 client rect of right:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":600,"y":-776,"width":100,"height":600,"top":-776,"right":700,"bottom":-176,"left":600}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":700,"y":0,"width":100,"height":600,"top":0,"right":800,"bottom":600,"left":700}
 
 Scrolled to 50, 300
 JSON.stringify(internals.layoutViewportRect()) is {"x":0,"y":0,"width":800,"height":600,"top":0,"right":800,"bottom":600,"left":0}
 JSON.stringify(internals.visualViewportRect()) is {"x":50,"y":257,"width":400,"height":300,"top":257,"right":450,"bottom":557,"left":50}
 client rect of top:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-50,"y":-300,"width":800,"height":100,"top":-300,"right":750,"bottom":-200,"left":-50}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":800,"height":100,"top":0,"right":800,"bottom":100,"left":0}
 client rect of bottom:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-50,"y":200,"width":800,"height":100,"top":200,"right":750,"bottom":300,"left":-50}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":500,"width":800,"height":100,"top":500,"right":800,"bottom":600,"left":0}
 client rect of left:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":-50,"y":-300,"width":100,"height":600,"top":-300,"right":50,"bottom":300,"left":-50}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":0,"y":0,"width":100,"height":600,"top":0,"right":100,"bottom":600,"left":0}
 client rect of right:
-JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":650,"y":-300,"width":100,"height":600,"top":-300,"right":750,"bottom":300,"left":650}
+JSON.stringify(fixedElement.getBoundingClientRect()) is {"x":700,"y":0,"width":100,"height":600,"top":0,"right":800,"bottom":600,"left":700}
 PASS successfullyParsed is true
 
 TEST COMPLETE
index cc30782..67dec6a 100644 (file)
@@ -1,3 +1,56 @@
+2017-05-11  Simon Fraser  <simon.fraser@apple.com>
+
+        Incorrect position when dragging jQuery Draggable elements with position fixed after pinch zoom
+        https://bugs.webkit.org/show_bug.cgi?id=171113
+        rdar://problem/31746516
+
+        Reviewed by Tim Horton.
+
+        Make getBoundingClientRect() and getClientRects() return rects which are relative to the layout
+        viewport, rather than the visual viewport. This goes part of the way to fixing webkit.org/b/170981,
+        which aims to make pinch-zoom invisible to web pages ("inert visual viewport"). It fixes issues on various
+        sites like Facebook when zoomed.
+
+        Factor coordinate conversion code into functions on FrameView, which now documents
+        the various coordinate systems in a big comment. Document::adjustFloatQuadsForScrollAndAbsoluteZoomAndFrameScale()
+        and Document::adjustFloatRectForScrollAndAbsoluteZoomAndFrameScale() are renamed and factored
+        to use these helpers.
+
+        There are two behavior changes here:
+
+        1. FrameView::documentToClientOffset() now uses the origin of the layout viewport in the "document to client"
+           coordinate mapping.
+           
+        2. The two document functions would apply the scale and offset in the wrong order. We need
+           to first undo the effects of CSS zoom, page zoom and page scale, and then map from document
+           to client coordinates.
+
+        Tests: fast/visual-viewport/client-rects-relative-to-layout-viewport.html
+               fast/zooming/client-rects-with-css-and-page-zoom.html
+
+        * dom/Document.cpp:
+        (WebCore::Document::convertAbsoluteToClientQuads):
+        (WebCore::Document::convertAbsoluteToClientRect):
+        (WebCore::Document::adjustFloatQuadsForScrollAndAbsoluteZoomAndFrameScale): Deleted.
+        (WebCore::Document::adjustFloatRectForScrollAndAbsoluteZoomAndFrameScale): Deleted.
+        * dom/Document.h:
+        * dom/Element.cpp:
+        (WebCore::Element::getClientRects):
+        (WebCore::Element::getBoundingClientRect):
+        * dom/Range.cpp:
+        (WebCore::Range::borderAndTextQuads):
+        * page/FrameView.cpp:
+        (WebCore::FrameView::absoluteToDocumentScaleFactor):
+        (WebCore::FrameView::absoluteToDocumentRect):
+        (WebCore::FrameView::absoluteToDocumentPoint):
+        (WebCore::FrameView::documentToClientOffset):
+        (WebCore::FrameView::documentToClientRect):
+        (WebCore::FrameView::documentToClientPoint):
+        * page/FrameView.h:
+        * platform/ScrollableArea.h: #pragma once
+        * platform/Scrollbar.h: #pragma once
+        * platform/Widget.h: #pragma once
+
 2017-05-12  Mark Lam  <mark.lam@apple.com>
 
         WorkerRunLoop::Task::performTask() should check !scriptController->isTerminatingExecution().
index ae951dc..64a98ce 100644 (file)
@@ -6409,42 +6409,31 @@ Element* eventTargetElementForDocument(Document* document)
     return element;
 }
 
-void Document::adjustFloatQuadsForScrollAndAbsoluteZoomAndFrameScale(Vector<FloatQuad>& quads, const RenderStyle& style)
+void Document::convertAbsoluteToClientQuads(Vector<FloatQuad>& quads, const RenderStyle& style)
 {
     if (!view())
         return;
 
-    float zoom = style.effectiveZoom();
-    float inverseFrameScale = 1;
-    if (frame())
-        inverseFrameScale = 1 / frame()->frameScaleFactor();
+    const auto& frameView = *view();
+    float inverseFrameScale = frameView.absoluteToDocumentScaleFactor(style.effectiveZoom());
+    auto documentToClientOffset = frameView.documentToClientOffset();
 
-    LayoutRect visibleContentRect = view()->visibleContentRect();
     for (auto& quad : quads) {
-        quad.move(-visibleContentRect.x(), -visibleContentRect.y());
-        if (zoom != 1)
-            quad.scale(1 / zoom);
         if (inverseFrameScale != 1)
             quad.scale(inverseFrameScale);
+
+        quad.move(documentToClientOffset);
     }
 }
 
-void Document::adjustFloatRectForScrollAndAbsoluteZoomAndFrameScale(FloatRect& rect, const RenderStyle& style)
+void Document::convertAbsoluteToClientRect(FloatRect& rect, const RenderStyle& style)
 {
     if (!view())
         return;
 
-    float zoom = style.effectiveZoom();
-    float inverseFrameScale = 1;
-    if (frame())
-        inverseFrameScale = 1 / frame()->frameScaleFactor();
-
-    LayoutRect visibleContentRect = view()->visibleContentRect();
-    rect.move(-visibleContentRect.x(), -visibleContentRect.y());
-    if (zoom != 1)
-        rect.scale(1 / zoom);
-    if (inverseFrameScale != 1)
-        rect.scale(inverseFrameScale);
+    const auto& frameView = *view();
+    rect = frameView.absoluteToDocumentRect(rect, style.effectiveZoom()); 
+    rect = frameView.documentToClientRect(rect);
 }
 
 bool Document::hasActiveParser()
index ab8a4da..44ba181 100644 (file)
@@ -1199,8 +1199,8 @@ public:
     IntSize initialViewportSize() const;
 #endif
 
-    void adjustFloatQuadsForScrollAndAbsoluteZoomAndFrameScale(Vector<FloatQuad>&, const RenderStyle&);
-    void adjustFloatRectForScrollAndAbsoluteZoomAndFrameScale(FloatRect&, const RenderStyle&);
+    void convertAbsoluteToClientQuads(Vector<FloatQuad>&, const RenderStyle&);
+    void convertAbsoluteToClientRect(FloatRect&, const RenderStyle&);
 
     bool hasActiveParser();
     void incrementActiveParserCount() { ++m_activeParserCount; }
index f71bacc..c87a6d6 100644 (file)
@@ -1157,7 +1157,7 @@ Vector<Ref<DOMRect>> Element::getClientRects()
 
     Vector<FloatQuad> quads;
     renderBoxModelObject->absoluteQuads(quads);
-    document().adjustFloatQuadsForScrollAndAbsoluteZoomAndFrameScale(quads, renderBoxModelObject->style());
+    document().convertAbsoluteToClientQuads(quads, renderBoxModelObject->style());
     return createDOMRectVector(quads);
 }
 
@@ -1185,10 +1185,11 @@ Ref<DOMRect> Element::getBoundingClientRect()
     for (size_t i = 1; i < quads.size(); ++i)
         result.unite(quads[i].boundingBox());
 
-    document().adjustFloatRectForScrollAndAbsoluteZoomAndFrameScale(result, renderer()->style());
+    document().convertAbsoluteToClientRect(result, renderer()->style());
     return DOMRect::create(result);
 }
 
+// Note that this is not web-exposed, and does not use the same coordinate system as getBoundingClientRect() and friends.
 IntRect Element::clientRect() const
 {
     if (RenderObject* renderer = this->renderer())
index f47d22d..82f589e 100644 (file)
@@ -1797,7 +1797,7 @@ Vector<FloatQuad> Range::borderAndTextQuads(CoordinateSpace space) const
                 Vector<FloatQuad> elementQuads;
                 renderer->absoluteQuads(elementQuads);
                 if (space == CoordinateSpace::Client)
-                    node->document().adjustFloatQuadsForScrollAndAbsoluteZoomAndFrameScale(elementQuads, renderer->style());
+                    node->document().convertAbsoluteToClientQuads(elementQuads, renderer->style());
                 quads.appendVector(elementQuads);
             }
         } else if (is<Text>(*node)) {
@@ -1806,7 +1806,7 @@ Vector<FloatQuad> Range::borderAndTextQuads(CoordinateSpace space) const
                 unsigned endOffset = node == &endContainer() ? m_end.offset() : std::numeric_limits<unsigned>::max();
                 auto textQuads = renderer->absoluteQuadsForRange(startOffset, endOffset);
                 if (space == CoordinateSpace::Client)
-                    node->document().adjustFloatQuadsForScrollAndAbsoluteZoomAndFrameScale(textQuads, renderer->style());
+                    node->document().convertAbsoluteToClientQuads(textQuads, renderer->style());
                 quads.appendVector(textQuads);
             }
         }
index 3661a60..2af6cd7 100644 (file)
@@ -4906,6 +4906,42 @@ IntPoint FrameView::convertFromContainingView(const IntPoint& parentPoint) const
     return parentPoint;
 }
 
+float FrameView::absoluteToDocumentScaleFactor(std::optional<float> effectiveZoom) const
+{
+    // If effectiveZoom is passed, it already factors in pageZoomFactor(). 
+    float cssZoom = effectiveZoom.value_or(frame().pageZoomFactor()); 
+    return 1 / (cssZoom * frame().frameScaleFactor());
+}
+
+FloatRect FrameView::absoluteToDocumentRect(FloatRect rect, std::optional<float> effectiveZoom) const
+{
+    rect.scale(absoluteToDocumentScaleFactor(effectiveZoom));
+    return rect;
+}
+
+FloatPoint FrameView::absoluteToDocumentPoint(FloatPoint p, std::optional<float> effectiveZoom) const
+{
+    p.scale(absoluteToDocumentScaleFactor(effectiveZoom));
+    return p;
+}
+
+FloatSize FrameView::documentToClientOffset() const
+{
+    return frame().settings().visualViewportEnabled() ? -toFloatSize(layoutViewportRect().location()) : -toFloatSize(visibleContentRect().location());
+}
+
+FloatRect FrameView::documentToClientRect(FloatRect rect) const
+{
+    rect.move(documentToClientOffset());
+    return rect;
+}
+
+FloatPoint FrameView::documentToClientPoint(FloatPoint p) const
+{
+    p.move(documentToClientOffset());
+    return p;
+}
+
 void FrameView::setTracksRepaints(bool trackRepaints)
 {
     if (trackRepaints == m_isTrackingRepaints)
index ca6b403..0a53690 100644 (file)
@@ -429,6 +429,30 @@ public:
     void maintainScrollPositionAtAnchor(ContainerNode*);
     WEBCORE_EXPORT void scrollElementToRect(const Element&, const IntRect&);
 
+    // Coordinate systems:
+    //
+    // "View"
+    //     Top left is top left of the FrameView/ScrollView/Widget. Size is Widget::boundsRect().size(). 
+    //
+    // "TotalContents"
+    //    Relative to ScrollView's scrolled contents, including headers and footers. Size is totalContentsSize().
+    //
+    // "Contents"
+    //    Relative to ScrollView's scrolled contents, excluding headers and footers, so top left is top left of the scroll view's
+    //    document, and size is contentsSize().
+    //
+    // "Absolute"
+    //    Relative to the document's scroll origin (non-zero for RTL documents), but affected by page zoom and page scale. Mostly used
+    //    in rendering code.
+    //
+    // "Document"
+    //    Relative to the document's scroll origin, but not affected by page zoom or page scale. Size is equivalent to CSS pixel dimensions.
+    //
+    // "Client"
+    //    Relative to the visible part of the document (or, more strictly, the layout viewport rect), and with the same scaling
+    //    as Document coordinates, i.e. matching CSS pixels. Affected by scroll origin.
+    //    
+
     // Methods to convert points and rects between the coordinate space of the renderer, and this view.
     WEBCORE_EXPORT IntRect convertFromRendererToContainingView(const RenderElement*, const IntRect&) const;
     WEBCORE_EXPORT IntRect convertFromContainingViewToRenderer(const RenderElement*, const IntRect&) const;
@@ -441,6 +465,14 @@ public:
     IntPoint convertToContainingView(const IntPoint&) const final;
     IntPoint convertFromContainingView(const IntPoint&) const final;
 
+    float absoluteToDocumentScaleFactor(std::optional<float> effectiveZoom = std::nullopt) const;
+    FloatRect absoluteToDocumentRect(FloatRect, std::optional<float> effectiveZoom = std::nullopt) const;
+    FloatPoint absoluteToDocumentPoint(FloatPoint, std::optional<float> effectiveZoom = std::nullopt) const;
+
+    FloatSize documentToClientOffset() const;
+    FloatRect documentToClientRect(FloatRect) const;
+    FloatPoint documentToClientPoint(FloatPoint) const;
+
     bool isFrameViewScrollCorner(const RenderScrollbarPart& scrollCorner) const { return m_scrollCorner == &scrollCorner; }
 
     // isScrollable() takes an optional Scrollability parameter that allows the caller to define what they mean by 'scrollable.'
index 24f8b16..77ff588 100644 (file)
@@ -23,8 +23,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
  */
 
-#ifndef ScrollableArea_h
-#define ScrollableArea_h
+#pragma once
 
 #include "ScrollSnapOffsetsInfo.h"
 #include "Scrollbar.h"
@@ -391,4 +390,3 @@ private:
 
 } // namespace WebCore
 
-#endif // ScrollableArea_h
index a3d7d9f..e256081 100644 (file)
@@ -23,8 +23,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
  */
 
-#ifndef Scrollbar_h
-#define Scrollbar_h
+#pragma once
 
 #include "ScrollTypes.h"
 #include "Timer.h"
@@ -192,4 +191,3 @@ private:
 
 SPECIALIZE_TYPE_TRAITS_WIDGET(Scrollbar, isScrollbar())
 
-#endif // Scrollbar_h
index dfefe09..e2e8e93 100644 (file)
@@ -24,8 +24,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef Widget_h
-#define Widget_h
+#pragma once
 
 #if PLATFORM(IOS)
 #ifndef NSView
@@ -261,4 +260,3 @@ SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::ToValueTypeName) \
     static bool isType(const WebCore::Widget& widget) { return widget.predicate; } \
 SPECIALIZE_TYPE_TRAITS_END()
 
-#endif // Widget_h