[iOS WK2] Can't place caret or select in content that overflows a contenteditable element
https://bugs.webkit.org/show_bug.cgi?id=199741
rdar://problem/50545233

Reviewed by Wenson Hsieh.
Source/WebCore:

Various code paths for editing used renderer->absoluteBoundingBoxRect(), which is the border
box of the element (or a set of line boxes for inline elements) converted to absolute
coordinates. This excludes overflow content, but contenteditable needs to be able to
place the caret in overflow content, and allow selection rects to be in the overflow area
(if the element has visible overflow).

Try to clean this up by adding some static helpers on WebPage for accessing the relevant
rects, and use them in code call from visiblePositionInFocusedNodeForPoint(), and
code that is input to selectionClipRect.

This changes selectionClipRect to use the padding box (excluding borders), which is a progression.

Tests: editing/caret/ios/caret-in-overflow-area.html
       editing/selection/ios/place-selection-in-overflow-area.html
       editing/selection/ios/selection-extends-into-overflow-area.html

* editing/FrameSelection.cpp:
(WebCore::DragCaretController::editableElementRectInRootViewCoordinates const):

Source/WebKit:

Various code paths for editing used renderer->absoluteBoundingBoxRect(), which is the border
box of the element (or a set of line boxes for inline elements) converted to absolute
coordinates. This excludes overflow content, but contenteditable needs to be able to
place the caret in overflow content, and allow selection rects to be in the overflow area
(if the element has visible overflow).

Try to clean this up by adding some static helpers on WebPage for accessing the relevant
rects, and use them in code call from visiblePositionInFocusedNodeForPoint(), and
code that is input to selectionClipRect.

This changes selectionClipRect to use the padding box (excluding borders), which is a progression.

* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::platformEditorState const):
(WebKit::elementBoundsInFrame):
(WebKit::constrainPoint):
(WebKit::WebPage::rootViewBoundsForElement):
(WebKit::WebPage::absoluteInteractionBoundsForElement):
(WebKit::WebPage::rootViewInteractionBoundsForElement):
(WebKit::WebPage::dispatchSyntheticMouseEventsForSelectionGesture):
(WebKit::WebPage::getFocusedElementInformation):
(WebKit::innerFrameQuad): Deleted.
(WebKit::elementRectInRootViewCoordinates): Deleted.

LayoutTests:

Re-enable editing/caret/ios, fixing the result of emoji.html which for some reason was
checked in as an html file (the test still fails).

* editing/caret/ios/caret-in-overflow-area-expected.txt: Added.
* editing/caret/ios/caret-in-overflow-area.html: Added.
* editing/caret/ios/emoji-expected.txt: Renamed from LayoutTests/editing/caret/ios/emoji-expected.html.
* editing/caret/ios/fixed-caret-position-after-scroll-expected.txt:
* editing/caret/ios/fixed-caret-position-after-scroll.html:
* editing/selection/ios/place-selection-in-overflow-area-expected.txt: Added.
* editing/selection/ios/place-selection-in-overflow-area.html: Added.
* editing/selection/ios/selection-extends-into-overflow-area-expected.txt: Added.
* editing/selection/ios/selection-extends-into-overflow-area.html: Added.
* platform/ios-wk2/TestExpectations:

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@247398 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index e5aa9e1..ca250d9 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -169,6 +169,28 @@
         https://bugs.webkit.org/show_bug.cgi?id=199644
         https://trac.webkit.org/changeset/247314
 
+2019-07-11  Simon Fraser  <simon.fraser@apple.com>
+
+        [iOS WK2] Can't place caret or select in content that overflows a contenteditable element
+        https://bugs.webkit.org/show_bug.cgi?id=199741
+        rdar://problem/50545233
+
+        Reviewed by Wenson Hsieh.
+        
+        Re-enable editing/caret/ios, fixing the result of emoji.html which for some reason was
+        checked in as an html file (the test still fails).
+
+        * editing/caret/ios/caret-in-overflow-area-expected.txt: Added.
+        * editing/caret/ios/caret-in-overflow-area.html: Added.
+        * editing/caret/ios/emoji-expected.txt: Renamed from LayoutTests/editing/caret/ios/emoji-expected.html.
+        * editing/caret/ios/fixed-caret-position-after-scroll-expected.txt:
+        * editing/caret/ios/fixed-caret-position-after-scroll.html:
+        * editing/selection/ios/place-selection-in-overflow-area-expected.txt: Added.
+        * editing/selection/ios/place-selection-in-overflow-area.html: Added.
+        * editing/selection/ios/selection-extends-into-overflow-area-expected.txt: Added.
+        * editing/selection/ios/selection-extends-into-overflow-area.html: Added.
+        * platform/ios-wk2/TestExpectations:
+
 2019-07-11  Ryan Haddad  <ryanhaddad@apple.com>
 
         Add test expectations and baselines for iOS 13
diff --git a/LayoutTests/editing/caret/ios/caret-in-overflow-area-expected.txt b/LayoutTests/editing/caret/ios/caret-in-overflow-area-expected.txt
new file mode 100644
index 0000000..891da13
--- /dev/null
+++ b/LayoutTests/editing/caret/ios/caret-in-overflow-area-expected.txt
@@ -0,0 +1,27 @@
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+Caret rect: (left = 39, top = 386, width = 2, height = 19)
diff --git a/LayoutTests/editing/caret/ios/caret-in-overflow-area.html b/LayoutTests/editing/caret/ios/caret-in-overflow-area.html
new file mode 100644
index 0000000..83d2693
--- /dev/null
+++ b/LayoutTests/editing/caret/ios/caret-in-overflow-area.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
+<html>
+<head>
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no">
+    <title></title>
+    <style>
+        #editable {
+            width: 300px;
+            height: 300px;
+            border: 1px solid black;
+            font-family: monospace;
+            line-height: 1.5em;
+        }
+        
+        .output {
+            margin-left: 60px;
+        }
+    </style>
+    <script src="../../../resources/ui-helper.js"></script>
+    <script>
+        if (window.testRunner) {
+            testRunner.dumpAsText();
+            testRunner.waitUntilDone();
+        }
+
+        function rectToString(rect)
+        {
+            return `(left = ${Math.round(rect.left)}, top = ${Math.round(rect.top)}, width = ${Math.round(rect.width)}, height = ${Math.round(rect.height)})`;
+        }
+
+        async function runTest()
+        {
+            let container = document.getElementById('editable');
+            await UIHelper.activateAndWaitForInputSessionAt(25, 25);
+
+            await UIHelper.tapAt(25, 400);
+            const caretRect = await UIHelper.getUICaretRect();
+
+            document.querySelector("#caret-rect").textContent = rectToString(caretRect);
+
+            if (window.testRunner)
+                testRunner.notifyDone();
+        }
+        
+        window.addEventListener('load', runTest, false);
+    </script>
+</head>
+<body>
+    <div id="editable" contenteditable>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+    </div>
+
+<div class="output">Caret rect: <span id="caret-rect"></span></div>
+
+</body>
+</html>
diff --git a/LayoutTests/editing/caret/ios/emoji-expected.html b/LayoutTests/editing/caret/ios/emoji-expected.txt
similarity index 100%
rename from LayoutTests/editing/caret/ios/emoji-expected.html
rename to LayoutTests/editing/caret/ios/emoji-expected.txt
diff --git a/LayoutTests/editing/caret/ios/fixed-caret-position-after-scroll-expected.txt b/LayoutTests/editing/caret/ios/fixed-caret-position-after-scroll-expected.txt
index d473645..3d3490e 100644
--- a/LayoutTests/editing/caret/ios/fixed-caret-position-after-scroll-expected.txt
+++ b/LayoutTests/editing/caret/ios/fixed-caret-position-after-scroll-expected.txt
@@ -3,7 +3,7 @@
 PASS initialCaretRect.width is 3
 PASS initialCaretRect.height is 15
 PASS finalCaretRect.left is 6
-PASS finalCaretRect.top is 5021
+PASS finalCaretRect.top is 4977
 PASS finalCaretRect.width is 3
 PASS finalCaretRect.height is 15
 PASS successfullyParsed is true
diff --git a/LayoutTests/editing/caret/ios/fixed-caret-position-after-scroll.html b/LayoutTests/editing/caret/ios/fixed-caret-position-after-scroll.html
index fa5b27a..6a60323 100644
--- a/LayoutTests/editing/caret/ios/fixed-caret-position-after-scroll.html
+++ b/LayoutTests/editing/caret/ios/fixed-caret-position-after-scroll.html
@@ -51,7 +51,7 @@
     .then((rect) => {
         finalCaretRect = rect;
         shouldBe("finalCaretRect.left", "6");
-        shouldBe("finalCaretRect.top", "5021");
+        shouldBe("finalCaretRect.top", "4977");
         shouldBe("finalCaretRect.width", "3");
         shouldBe("finalCaretRect.height", "15");
         finishJSTest();
diff --git a/LayoutTests/editing/selection/ios/place-selection-in-overflow-area-expected.txt b/LayoutTests/editing/selection/ios/place-selection-in-overflow-area-expected.txt
new file mode 100644
index 0000000..11b5a5b
--- /dev/null
+++ b/LayoutTests/editing/selection/ios/place-selection-in-overflow-area-expected.txt
@@ -0,0 +1,28 @@
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+Selection before tap: (node at 2#11, [object Text]#11)
+Selection after tap: (node at 40#13, [object Text]#13)
diff --git a/LayoutTests/editing/selection/ios/place-selection-in-overflow-area.html b/LayoutTests/editing/selection/ios/place-selection-in-overflow-area.html
new file mode 100644
index 0000000..6323668
--- /dev/null
+++ b/LayoutTests/editing/selection/ios/place-selection-in-overflow-area.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
+<html>
+<head>
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no">
+    <title></title>
+    <style>
+        #editable {
+            width: 300px;
+            height: 300px;
+            border: 1px solid black;
+            font-family: monospace;
+            line-height: 1.5em;
+        }
+        
+        .output {
+            margin-left: 60px;
+        }
+    </style>
+    <script src="../../../resources/ui-helper.js"></script>
+    <script>
+        if (window.testRunner) {
+            testRunner.dumpAsText();
+            testRunner.waitUntilDone();
+        }
+
+        function selectionToString(container)
+        {
+            const selection = getSelection();
+            if (!selection.rangeCount)
+                return "(no selection)";
+
+            const range = selection.getRangeAt(0);
+            const nodeIndex = Array.prototype.indexOf.call(container.childNodes, range.startContainer);
+
+            return `(node at ${nodeIndex}#${range.startOffset}, ${range.endContainer}#${range.endOffset})`;
+        }
+
+        async function runTest()
+        {
+            let container = document.getElementById('editable');
+            await UIHelper.activateAndWaitForInputSessionAt(25, 25);
+            document.querySelector("#selection-before").textContent = selectionToString(container);
+
+            await UIHelper.tapAt(25, 400);
+            document.querySelector("#selection-after").textContent = selectionToString(container);
+
+            if (window.testRunner)
+                testRunner.notifyDone();
+        }
+        
+        window.addEventListener('load', runTest, false);
+    </script>
+</head>
+<body>
+    <div id="editable" contenteditable>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+    </div>
+
+<div class="output">Selection before tap: <span id="selection-before"></span></div>
+<div class="output">Selection after tap: <span id="selection-after"></span></div>
+
+</body>
+</html>
diff --git a/LayoutTests/editing/selection/ios/selection-extends-into-overflow-area-expected.txt b/LayoutTests/editing/selection/ios/selection-extends-into-overflow-area-expected.txt
new file mode 100644
index 0000000..5c7dee3
--- /dev/null
+++ b/LayoutTests/editing/selection/ios/selection-extends-into-overflow-area-expected.txt
@@ -0,0 +1,27 @@
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+line
+Selection rects: (left = 9, top = 386, width = 32, height = 19)
diff --git a/LayoutTests/editing/selection/ios/selection-extends-into-overflow-area.html b/LayoutTests/editing/selection/ios/selection-extends-into-overflow-area.html
new file mode 100644
index 0000000..73fcbeb
--- /dev/null
+++ b/LayoutTests/editing/selection/ios/selection-extends-into-overflow-area.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
+<html>
+<head>
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no">
+    <title></title>
+    <style>
+        #editable {
+            width: 300px;
+            height: 300px;
+            border: 1px solid black;
+            font-family: monospace;
+            line-height: 1.5em;
+        }
+        
+        .output {
+            margin-left: 60px;
+        }
+    </style>
+    <script src="../../../resources/ui-helper.js"></script>
+    <script>
+        if (window.testRunner) {
+            testRunner.dumpAsText();
+            testRunner.waitUntilDone();
+        }
+
+        function selectTextAt(tapX, tapY)
+        {
+            return new Promise(resolve => {
+                testRunner.runUIScript(`
+                    (function() {
+                        uiController.longPressAtPoint(${tapX}, ${tapY}, function() {
+                            uiController.uiScriptComplete("Done");
+                        });
+                    })();`, resolve);
+            });
+        }
+
+        function rectToString(rect)
+        {
+            return `(left = ${Math.round(rect.left)}, top = ${Math.round(rect.top)}, width = ${Math.round(rect.width)}, height = ${Math.round(rect.height)})`;
+        }
+
+        async function runTest()
+        {
+            let container = document.getElementById('editable');
+            await UIHelper.activateAndWaitForInputSessionAt(25, 25);
+
+            await selectTextAt(25, 400)
+            const selectionRects = await UIHelper.getUISelectionRects();
+            let rectsString = "";
+            for (let rect of selectionRects)
+                rectsString += rectToString(rect) + ' ';
+
+            document.querySelector("#selection-rects").textContent = rectsString;
+            if (window.testRunner)
+                testRunner.notifyDone();
+        }
+        
+        window.addEventListener('load', runTest, false);
+    </script>
+</head>
+<body>
+    <div id="editable" contenteditable>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+        line<br>
+    </div>
+
+<div class="output">Selection rects: <span id="selection-rects"></span></div>
+
+</body>
+</html>
diff --git a/LayoutTests/platform/ios-wk2/TestExpectations b/LayoutTests/platform/ios-wk2/TestExpectations
index 2e681ee..3bcc4f0 100644
--- a/LayoutTests/platform/ios-wk2/TestExpectations
+++ b/LayoutTests/platform/ios-wk2/TestExpectations
@@ -18,6 +18,7 @@
 scrollingcoordinator [ Pass ]
 scrollingcoordinator/mac [ Skip ]
 fast/web-share [ Pass ]
+editing/caret/ios [ Pass ]
 editing/find [ Pass ]
 editing/input/ios [ Pass ]
 editing/pasteboard/ios [ Pass ]
@@ -694,6 +695,7 @@
 editing/undo/undo-delete.html [ Failure ]
 editing/undo/undo-misspellings.html [ Failure ]
 editing/undo/undo-typing-001.html [ Failure ]
+editing/caret/ios/emoji.html [ Failure ]
 
 # Editing tests that time out:
 editing/selection/designmode-no-caret.html
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 2c8c22a..956a97c 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -498,6 +498,33 @@
 
         * platform/graphics/cocoa/IOSurface.h:
 
+2019-07-11  Simon Fraser  <simon.fraser@apple.com>
+
+        [iOS WK2] Can't place caret or select in content that overflows a contenteditable element
+        https://bugs.webkit.org/show_bug.cgi?id=199741
+        rdar://problem/50545233
+
+        Reviewed by Wenson Hsieh.
+
+        Various code paths for editing used renderer->absoluteBoundingBoxRect(), which is the border
+        box of the element (or a set of line boxes for inline elements) converted to absolute
+        coordinates. This excludes overflow content, but contenteditable needs to be able to
+        place the caret in overflow content, and allow selection rects to be in the overflow area
+        (if the element has visible overflow).
+
+        Try to clean this up by adding some static helpers on WebPage for accessing the relevant
+        rects, and use them in code call from visiblePositionInFocusedNodeForPoint(), and
+        code that is input to selectionClipRect.
+        
+        This changes selectionClipRect to use the padding box (excluding borders), which is a progression.
+
+        Tests: editing/caret/ios/caret-in-overflow-area.html
+               editing/selection/ios/place-selection-in-overflow-area.html
+               editing/selection/ios/selection-extends-into-overflow-area.html
+
+        * editing/FrameSelection.cpp:
+        (WebCore::DragCaretController::editableElementRectInRootViewCoordinates const):
+
 2019-07-11  Jonathan Bedard  <jbedard@apple.com>
 
         [iOS 13] Enable WebKit build
diff --git a/Source/WebCore/editing/FrameSelection.cpp b/Source/WebCore/editing/FrameSelection.cpp
index 34de896..74fa64b 100644
--- a/Source/WebCore/editing/FrameSelection.cpp
+++ b/Source/WebCore/editing/FrameSelection.cpp
@@ -133,7 +133,7 @@
         return { };
 
     if (auto* view = editableContainer->document().view())
-        return view->contentsToRootView(renderer->absoluteBoundingBoxRect());
+        return view->contentsToRootView(renderer->absoluteBoundingBoxRect()); // FIXME: Wrong for elements with visible layout overflow.
 
     return { };
 }
diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog
index 58a5249..0669b3b 100644
--- a/Source/WebKit/ChangeLog
+++ b/Source/WebKit/ChangeLog
@@ -208,6 +208,39 @@
         * UIProcess/ios/WebPageProxyIOS.mm:
         (WebKit::desktopClassBrowsingRecommendedForRequest):
 
+2019-07-11  Simon Fraser  <simon.fraser@apple.com>
+
+        [iOS WK2] Can't place caret or select in content that overflows a contenteditable element
+        https://bugs.webkit.org/show_bug.cgi?id=199741
+        rdar://problem/50545233
+
+        Reviewed by Wenson Hsieh.
+
+        Various code paths for editing used renderer->absoluteBoundingBoxRect(), which is the border
+        box of the element (or a set of line boxes for inline elements) converted to absolute
+        coordinates. This excludes overflow content, but contenteditable needs to be able to
+        place the caret in overflow content, and allow selection rects to be in the overflow area
+        (if the element has visible overflow).
+
+        Try to clean this up by adding some static helpers on WebPage for accessing the relevant
+        rects, and use them in code call from visiblePositionInFocusedNodeForPoint(), and
+        code that is input to selectionClipRect.
+
+        This changes selectionClipRect to use the padding box (excluding borders), which is a progression.
+
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::WebPage::platformEditorState const):
+        (WebKit::elementBoundsInFrame):
+        (WebKit::constrainPoint):
+        (WebKit::WebPage::rootViewBoundsForElement):
+        (WebKit::WebPage::absoluteInteractionBoundsForElement):
+        (WebKit::WebPage::rootViewInteractionBoundsForElement):
+        (WebKit::WebPage::dispatchSyntheticMouseEventsForSelectionGesture):
+        (WebKit::WebPage::getFocusedElementInformation):
+        (WebKit::innerFrameQuad): Deleted.
+        (WebKit::elementRectInRootViewCoordinates): Deleted.
+
 2019-07-11  Jonathan Bedard  <jbedard@apple.com>
 
         [iOS 13] Enable WebKit build
diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.h b/Source/WebKit/WebProcess/WebPage/WebPage.h
index abfad62..97483e0 100644
--- a/Source/WebKit/WebProcess/WebPage/WebPage.h
+++ b/Source/WebKit/WebProcess/WebPage/WebPage.h
@@ -1202,7 +1202,15 @@
 
     bool firstFlushAfterCommit() const { return m_firstFlushAfterCommit; }
     void setFirstFlushAfterCommit(bool f) { m_firstFlushAfterCommit = f; }
-    
+
+#if PLATFORM(IOS_FAMILY)
+    // This excludes layout overflow, includes borders.
+    static WebCore::IntRect rootViewBoundsForElement(const WebCore::Element&);
+    // These include layout overflow for overflow:visible elements, but exclude borders.
+    static WebCore::IntRect absoluteInteractionBoundsForElement(const WebCore::Element&);
+    static WebCore::IntRect rootViewInteractionBoundsForElement(const WebCore::Element&);
+#endif // PLATFORM(IOS_FAMILY)
+
 private:
     WebPage(WebCore::PageIdentifier, WebPageCreationParameters&&);
 
@@ -1235,6 +1243,7 @@
     void updateViewportSizeForCSSViewportUnits();
 
     static void convertSelectionRectsToRootView(WebCore::FrameView*, Vector<WebCore::SelectionRect>&);
+
     void getFocusedElementInformation(FocusedElementInformation&);
     void platformInitializeAccessibility();
     void generateSyntheticEditingCommand(SyntheticEditingCommandType);
diff --git a/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm b/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
index 21e527a..873c9ff 100644
--- a/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
+++ b/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
@@ -264,7 +264,7 @@
     if (!selection.isNone()) {
         if (m_focusedElement && m_focusedElement->renderer()) {
             auto& renderer = *m_focusedElement->renderer();
-            postLayoutData.focusedElementRect = view->contentsToRootView(renderer.absoluteBoundingBoxRect());
+            postLayoutData.focusedElementRect = rootViewInteractionBoundsForElement(*m_focusedElement);
             postLayoutData.caretColor = renderer.style().caretColor();
         }
         if (result.isContentEditable) {
@@ -1248,33 +1248,24 @@
     m_viewportConfiguration.setForceAlwaysUserScalable(userScalable);
 }
 
-static FloatQuad innerFrameQuad(const Frame& frame, const Element& focusedElement)
+static IntRect elementBoundsInFrame(const Frame& frame, const Element& focusedElement)
 {
     frame.document()->updateLayoutIgnorePendingStylesheets();
-    RenderElement* renderer = nullptr;
-    if (focusedElement.hasTagName(HTMLNames::textareaTag) || focusedElement.hasTagName(HTMLNames::inputTag) || focusedElement.hasTagName(HTMLNames::selectTag))
-        renderer = focusedElement.renderer();
-    else if (auto* rootEditableElement = focusedElement.rootEditableElement())
-        renderer = rootEditableElement->renderer();
     
-    if (!renderer)
-        return FloatQuad();
+    if (focusedElement.hasTagName(HTMLNames::textareaTag) || focusedElement.hasTagName(HTMLNames::inputTag) || focusedElement.hasTagName(HTMLNames::selectTag))
+        return WebPage::absoluteInteractionBoundsForElement(focusedElement);
 
-    auto& style = renderer->style();
-    IntRect boundingBox = renderer->absoluteBoundingBoxRect(true /* use transforms*/);
+    if (auto* rootEditableElement = focusedElement.rootEditableElement())
+        return WebPage::absoluteInteractionBoundsForElement(*rootEditableElement);
 
-    boundingBox.move(style.borderLeftWidth(), style.borderTopWidth());
-    boundingBox.setWidth(boundingBox.width() - style.borderLeftWidth() - style.borderRightWidth());
-    boundingBox.setHeight(boundingBox.height() - style.borderBottomWidth() - style.borderTopWidth());
-
-    return FloatQuad(boundingBox);
+    return { };
 }
 
 static IntPoint constrainPoint(const IntPoint& point, const Frame& frame, const Element& focusedElement)
 {
     ASSERT(&focusedElement.document() == frame.document());
     const int DEFAULT_CONSTRAIN_INSET = 2;
-    IntRect innerFrame = innerFrameQuad(frame, focusedElement).enclosingBoundingBox();
+    IntRect innerFrame = elementBoundsInFrame(frame, focusedElement);
     IntPoint constrainedPoint = point;
 
     int minX = innerFrame.x() + DEFAULT_CONSTRAIN_INSET;
@@ -1568,7 +1559,7 @@
     return (base < extent) ? Range::create(*frame->document(), base, extent) : Range::create(*frame->document(), extent, base);
 }
 
-static IntRect elementRectInRootViewCoordinates(const Element& element)
+IntRect WebPage::rootViewBoundsForElement(const Element& element)
 {
     auto* frame = element.document().frame();
     if (!frame)
@@ -1585,6 +1576,54 @@
     return view->contentsToRootView(renderer->absoluteBoundingBoxRect());
 }
 
+IntRect WebPage::absoluteInteractionBoundsForElement(const Element& element)
+{
+    auto* frame = element.document().frame();
+    if (!frame)
+        return { };
+
+    auto* view = frame->view();
+    if (!view)
+        return { };
+
+    auto* renderer = element.renderer();
+    if (!renderer)
+        return { };
+
+    if (is<RenderBox>(*renderer)) {
+        auto& box = downcast<RenderBox>(*renderer);
+
+        FloatRect rect;
+        // FIXME: want borders or not?
+        if (box.style().isOverflowVisible())
+            rect = box.layoutOverflowRect();
+        else
+            rect = box.clientBoxRect();
+        return box.localToAbsoluteQuad(rect).enclosingBoundingBox();
+    }
+
+    auto& style = renderer->style();
+    FloatRect boundingBox = renderer->absoluteBoundingBoxRect(true /* use transforms*/);
+    // This is wrong. It's subtracting borders after converting to absolute coords on something that probably doesn't represent a rectangular element.
+    boundingBox.move(style.borderLeftWidth(), style.borderTopWidth());
+    boundingBox.setWidth(boundingBox.width() - style.borderLeftWidth() - style.borderRightWidth());
+    boundingBox.setHeight(boundingBox.height() - style.borderBottomWidth() - style.borderTopWidth());
+    return enclosingIntRect(boundingBox);
+}
+
+IntRect WebPage::rootViewInteractionBoundsForElement(const Element& element)
+{
+    auto* frame = element.document().frame();
+    if (!frame)
+        return { };
+
+    auto* view = frame->view();
+    if (!view)
+        return { };
+
+    return view->contentsToRootView(absoluteInteractionBoundsForElement(element));
+}
+
 void WebPage::clearSelection()
 {
     m_startingGestureRange = nullptr;
@@ -1599,7 +1638,7 @@
 
     IntRect focusedElementRect;
     if (m_focusedElement)
-        focusedElementRect = elementRectInRootViewCoordinates(*m_focusedElement);
+        focusedElementRect = rootViewInteractionBoundsForElement(*m_focusedElement);
 
     if (focusedElementRect.isEmpty())
         return;
@@ -2834,7 +2873,7 @@
     information.lastInteractionLocation = m_lastInteractionLocation;
 
     if (auto* renderer = m_focusedElement->renderer()) {
-        information.elementRect = elementRectInRootViewCoordinates(*m_focusedElement);
+        information.elementRect = rootViewInteractionBoundsForElement(*m_focusedElement);
         information.nodeFontSize = renderer->style().fontDescription().computedSize();
 
         bool inFixed = false;
@@ -2853,11 +2892,11 @@
     information.allowsUserScaling = m_viewportConfiguration.allowsUserScaling();
     information.allowsUserScalingIgnoringAlwaysScalable = m_viewportConfiguration.allowsUserScalingIgnoringAlwaysScalable();
     if (auto* nextElement = nextAssistableElement(m_focusedElement.get(), *m_page, true)) {
-        information.nextNodeRect = elementRectInRootViewCoordinates(*nextElement);
+        information.nextNodeRect = rootViewBoundsForElement(*nextElement);
         information.hasNextNode = true;
     }
     if (auto* previousElement = nextAssistableElement(m_focusedElement.get(), *m_page, false)) {
-        information.previousNodeRect = elementRectInRootViewCoordinates(*previousElement);
+        information.previousNodeRect = rootViewBoundsForElement(*previousElement);
         information.hasPreviousNode = true;
     }
     information.focusedElementIdentifier = m_currentFocusedElementIdentifier;
diff --git a/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm b/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm
index 350b626..256182d 100644
--- a/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm
+++ b/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm
@@ -521,10 +521,10 @@
     [webView synchronouslyLoadHTMLString:@"<meta name='viewport' content='width=device-width, initial-scale=1'><input>"];
     [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"document.querySelector('input').focus()"];
 
-    EXPECT_EQ(10, selectionClipRect.origin.x);
-    EXPECT_EQ(10, selectionClipRect.origin.y);
-    EXPECT_EQ(136, selectionClipRect.size.width);
-    EXPECT_EQ(22, selectionClipRect.size.height);
+    EXPECT_EQ(11, selectionClipRect.origin.x);
+    EXPECT_EQ(11, selectionClipRect.origin.y);
+    EXPECT_EQ(134, selectionClipRect.size.width);
+    EXPECT_EQ(20, selectionClipRect.size.height);
 }
 
 } // namespace TestWebKitAPI