Title: [257722] trunk
Revision
257722
Author
[email protected]
Date
2020-03-02 11:54:43 -0800 (Mon, 02 Mar 2020)

Log Message

Page::editableElementsInRect() should find nested editable elements and return found elements in paint order
https://bugs.webkit.org/show_bug.cgi?id=208352
<rdar://problem/59867815>

Reviewed by Wenson Hsieh.

Source/WebCore:

Use the existing rect-based hit testing machinery to collect all the editable elements on
the page that a person can hit. This makes it possible to find nested editable elements
(e.g. an <input> inside a <div contenteditable="true">), ignore elements with CSS "pointer-events: none",
and return elements in paint order (closest to farthest to a person's face). The latter
makes it possible for a caller to know what element is frontmost, especially when two
editable elements overlap.

* page/Page.cpp:
(WebCore::Page::editableElementsInRect const): Implement in terms of hit testing machinery.
* rendering/HitTestLocation.cpp:
(WebCore::HitTestLocation::HitTestLocation):
* rendering/HitTestLocation.h:
* rendering/HitTestResult.cpp:
(WebCore::HitTestResult::HitTestResult):
* rendering/HitTestResult.h:
Added convenience constructors that take a LayoutRect.

Tools:

Update test results to reflect the new behavior. To do this I also need to fix up the test code
to actually scroll the web content. This also means we now execute the same code on both Mac and
iOS to do the scrolling: window.scrollTo(0, 5000). Also fixed up sub-test "Inputs scrolled outside
the requested rect; should not be included." to use a 10000px height <div> so that you can scroll
to a y position of 5000. Otherwise, it's impossible to do because the page height is < 5000px.

* TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm:
(TEST):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (257721 => 257722)


--- trunk/Source/WebCore/ChangeLog	2020-03-02 19:46:55 UTC (rev 257721)
+++ trunk/Source/WebCore/ChangeLog	2020-03-02 19:54:43 UTC (rev 257722)
@@ -1,3 +1,28 @@
+2020-03-02  Daniel Bates  <[email protected]>
+
+        Page::editableElementsInRect() should find nested editable elements and return found elements in paint order
+        https://bugs.webkit.org/show_bug.cgi?id=208352
+        <rdar://problem/59867815>
+
+        Reviewed by Wenson Hsieh.
+
+        Use the existing rect-based hit testing machinery to collect all the editable elements on
+        the page that a person can hit. This makes it possible to find nested editable elements
+        (e.g. an <input> inside a <div contenteditable="true">), ignore elements with CSS "pointer-events: none",
+        and return elements in paint order (closest to farthest to a person's face). The latter
+        makes it possible for a caller to know what element is frontmost, especially when two
+        editable elements overlap.
+
+        * page/Page.cpp:
+        (WebCore::Page::editableElementsInRect const): Implement in terms of hit testing machinery.
+        * rendering/HitTestLocation.cpp:
+        (WebCore::HitTestLocation::HitTestLocation):
+        * rendering/HitTestLocation.h:
+        * rendering/HitTestResult.cpp:
+        (WebCore::HitTestResult::HitTestResult):
+        * rendering/HitTestResult.h:
+        Added convenience constructors that take a LayoutRect.
+
 2020-03-02  Doug Kelly  <[email protected]>
 
         ASSERT(m_column != unsetColumnIndex) in RenderTable::cellBefore

Modified: trunk/Source/WebCore/page/Page.cpp (257721 => 257722)


--- trunk/Source/WebCore/page/Page.cpp	2020-03-02 19:46:55 UTC (rev 257721)
+++ trunk/Source/WebCore/page/Page.cpp	2020-03-02 19:54:43 UTC (rev 257722)
@@ -128,7 +128,6 @@
 #include "VoidCallback.h"
 #include "WheelEventDeltaFilter.h"
 #include "Widget.h"
-#include <wtf/Deque.h>
 #include <wtf/FileSystem.h>
 #include <wtf/RefCountedLeakCounter.h>
 #include <wtf/StdLibExtras.h>
@@ -927,26 +926,29 @@
 
 Vector<Ref<Element>> Page::editableElementsInRect(const FloatRect& searchRectInRootViewCoordinates) const
 {
-    Vector<Ref<Element>> result;
-    forEachDocument([&] (Document& document) {
-        Deque<Node*> nodesToSearch;
-        nodesToSearch.append(&document);
-        while (!nodesToSearch.isEmpty()) {
-            auto* node = nodesToSearch.takeFirst();
+    auto frameView = makeRefPtr(mainFrame().view());
+    if (!frameView)
+        return { };
 
-            // It is possible to have nested text input contexts (e.g. <input type='text'> inside contenteditable) but
-            // in this case we just take the outermost context and skip the rest.
-            if (!is<Element>(node) || !isEditableTextInputElement(downcast<Element>(*node))) {
-                for (auto* child = node->firstChild(); child; child = child->nextSibling())
-                    nodesToSearch.append(child);
-                continue;
-            }
+    auto document = makeRefPtr(mainFrame().document());
+    if (!document)
+        return { };
 
-            auto& element = downcast<Element>(*node);
-            if (searchRectInRootViewCoordinates.intersects(element.clientRect()))
-                result.append(element);
+    constexpr OptionSet<HitTestRequest::RequestType> hitType { HitTestRequest::ReadOnly, HitTestRequest::Active, HitTestRequest::CollectMultipleElements, HitTestRequest::DisallowUserAgentShadowContent, HitTestRequest::AllowVisibleChildFrameContentOnly };
+    LayoutRect searchRectInMainFrameCoordinates = frameView->rootViewToContents(roundedIntRect(searchRectInRootViewCoordinates));
+    HitTestResult hitTestResult { searchRectInMainFrameCoordinates };
+    if (!document->hitTest(hitType, hitTestResult))
+        return { };
+
+    Vector<Ref<Element>> result;
+    auto& nodeSet = hitTestResult.listBasedTestResult();
+    result.reserveInitialCapacity(nodeSet.size());
+    for (auto& node : nodeSet) {
+        if (is<Element>(node) && isEditableTextInputElement(downcast<Element>(*node))) {
+            ASSERT(searchRectInRootViewCoordinates.intersects(downcast<Element>(*node).clientRect()));
+            result.uncheckedAppend(downcast<Element>(*node));
         }
-    });
+    }
     return result;
 }
 

Modified: trunk/Source/WebCore/rendering/HitTestLocation.cpp (257721 => 257722)


--- trunk/Source/WebCore/rendering/HitTestLocation.cpp	2020-03-02 19:46:55 UTC (rev 257721)
+++ trunk/Source/WebCore/rendering/HitTestLocation.cpp	2020-03-02 19:54:43 UTC (rev 257722)
@@ -48,6 +48,15 @@
 {
 }
 
+HitTestLocation::HitTestLocation(const LayoutRect& rect)
+    : m_point { rect.center() } // Rounded to an integer point; it may not be the exact center.
+    , m_boundingBox { rect }
+    , m_transformedPoint { rect.center() }
+    , m_transformedRect { FloatQuad { m_boundingBox } }
+    , m_isRectBased { true }
+{
+}
+
 HitTestLocation::HitTestLocation(const LayoutPoint& centerPoint, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding)
     : m_point(centerPoint)
     , m_boundingBox(rectForPoint(centerPoint, topPadding, rightPadding, bottomPadding, leftPadding))

Modified: trunk/Source/WebCore/rendering/HitTestLocation.h (257721 => 257722)


--- trunk/Source/WebCore/rendering/HitTestLocation.h	2020-03-02 19:46:55 UTC (rev 257721)
+++ trunk/Source/WebCore/rendering/HitTestLocation.h	2020-03-02 19:54:43 UTC (rev 257722)
@@ -31,8 +31,10 @@
     HitTestLocation(const LayoutPoint&);
     WEBCORE_EXPORT HitTestLocation(const FloatPoint&);
     HitTestLocation(const FloatPoint&, const FloatQuad&);
-    // Pass non-zero padding values to perform a rect-based hit test.
+
+    HitTestLocation(const LayoutRect&);
     HitTestLocation(const LayoutPoint& centerPoint, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding);
+
     // Make a copy the HitTestLocation in a new region by applying given offset to internal point and area.
     HitTestLocation(const HitTestLocation&, const LayoutSize& offset);
     WEBCORE_EXPORT HitTestLocation(const HitTestLocation&);

Modified: trunk/Source/WebCore/rendering/HitTestResult.cpp (257721 => 257722)


--- trunk/Source/WebCore/rendering/HitTestResult.cpp	2020-03-02 19:46:55 UTC (rev 257721)
+++ trunk/Source/WebCore/rendering/HitTestResult.cpp	2020-03-02 19:54:43 UTC (rev 257722)
@@ -62,6 +62,12 @@
 {
 }
 
+HitTestResult::HitTestResult(const LayoutRect& rect)
+    : m_hitTestLocation { rect }
+    , m_pointInInnerNodeFrame { rect.center() }
+{
+}
+
 HitTestResult::HitTestResult(const LayoutPoint& centerPoint, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding)
     : m_hitTestLocation(centerPoint, topPadding, rightPadding, bottomPadding, leftPadding)
     , m_pointInInnerNodeFrame(centerPoint)

Modified: trunk/Source/WebCore/rendering/HitTestResult.h (257721 => 257722)


--- trunk/Source/WebCore/rendering/HitTestResult.h	2020-03-02 19:46:55 UTC (rev 257721)
+++ trunk/Source/WebCore/rendering/HitTestResult.h	2020-03-02 19:54:43 UTC (rev 257722)
@@ -44,8 +44,10 @@
 
     WEBCORE_EXPORT HitTestResult();
     WEBCORE_EXPORT explicit HitTestResult(const LayoutPoint&);
-    // Pass non-negative padding values to perform a rect-based hit test.
+
+    WEBCORE_EXPORT explicit HitTestResult(const LayoutRect&);
     WEBCORE_EXPORT HitTestResult(const LayoutPoint& centerPoint, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding);
+
     WEBCORE_EXPORT explicit HitTestResult(const HitTestLocation&);
     WEBCORE_EXPORT HitTestResult(const HitTestResult&);
     WEBCORE_EXPORT ~HitTestResult();

Modified: trunk/Tools/ChangeLog (257721 => 257722)


--- trunk/Tools/ChangeLog	2020-03-02 19:46:55 UTC (rev 257721)
+++ trunk/Tools/ChangeLog	2020-03-02 19:54:43 UTC (rev 257722)
@@ -1,3 +1,20 @@
+2020-03-02  Daniel Bates  <[email protected]>
+
+        Page::editableElementsInRect() should find nested editable elements and return found elements in paint order
+        https://bugs.webkit.org/show_bug.cgi?id=208352
+        <rdar://problem/59867815>
+
+        Reviewed by Wenson Hsieh.
+
+        Update test results to reflect the new behavior. To do this I also need to fix up the test code
+        to actually scroll the web content. This also means we now execute the same code on both Mac and
+        iOS to do the scrolling: window.scrollTo(0, 5000). Also fixed up sub-test "Inputs scrolled outside
+        the requested rect; should not be included." to use a 10000px height <div> so that you can scroll
+        to a y position of 5000. Otherwise, it's impossible to do because the page height is < 5000px.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm:
+        (TEST):
+
 2020-03-02  Jacob Uphoff  <[email protected]>
 
         Unreviewed, rolling out r257687.

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm (257721 => 257722)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm	2020-03-02 19:46:55 UTC (rev 257721)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm	2020-03-02 19:54:43 UTC (rev 257722)
@@ -76,7 +76,6 @@
 TEST(WebKit, RequestTextInputContext)
 {
     RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
-    WKPreferencesSetThreadedScrollingEnabled((WKPreferencesRef)[configuration preferences], false);
     RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
 
     NSArray<_WKTextInputContext *> *contexts;
@@ -133,12 +132,10 @@
 
     // Inputs scrolled outside the requested rect; should not be included.
 
-    [webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' style='width: 50px; height: 50px;'><br><div style='width: 100px; height: 5000px;'></div>")];
-#if PLATFORM(MAC)
-    [webView objectByEvaluatingJavaScript:@"window.scrollTo(0, 5000);"];
-#else
-    [webView scrollView].contentOffset = CGPointMake(0, 5000);
-#endif
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' style='width: 50px; height: 50px;'><br><div style='width: 100px; height: 10000px;'></div>")];
+    [webView stringByEvaluatingJavaScript:@"window.scrollTo(0, 5000)"];
+    [webView waitForNextPresentationUpdate];
+    EXPECT_EQ(5000, [[webView objectByEvaluatingJavaScript:@"pageYOffset"] floatValue]);
     contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
     EXPECT_EQ(0UL, contexts.count);
 
@@ -145,11 +142,9 @@
     // Inputs scrolled into the requested rect.
 
     [webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' style='width: 50px; height: 50px; position: absolute; top: 5000px;'><br><div style='width: 100px; height: 10000px;'></div>")];
-#if PLATFORM(MAC)
-    [webView objectByEvaluatingJavaScript:@"window.scrollTo(0, 5000);"];
-#else
-    [webView scrollView].contentOffset = CGPointMake(0, 5000);
-#endif
+    [webView stringByEvaluatingJavaScript:@"window.scrollTo(0, 5000)"];
+    [webView waitForNextPresentationUpdate];
+    EXPECT_EQ(5000, [[webView objectByEvaluatingJavaScript:@"pageYOffset"] floatValue]);
     contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
     EXPECT_EQ(1UL, contexts.count);
     EXPECT_EQ(CGRectMake(0, 0, 50, 50), contexts[0].boundingRect);
@@ -159,16 +154,17 @@
     [webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' style='width: 50px; height: 50px;'><br/><input type='text' style='width: 50px; height: 50px;'><br/><input type='text' style='width: 50px; height: 50px;'>")];
     contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
     EXPECT_EQ(3UL, contexts.count);
-    EXPECT_EQ(CGRectMake(0, 0, 50, 50), contexts[0].boundingRect);
+    EXPECT_EQ(CGRectMake(0, 100, 50, 50), contexts[0].boundingRect);
     EXPECT_EQ(CGRectMake(0, 50, 50, 50), contexts[1].boundingRect);
-    EXPECT_EQ(CGRectMake(0, 100, 50, 50), contexts[2].boundingRect);
+    EXPECT_EQ(CGRectMake(0, 0, 50, 50), contexts[2].boundingRect);
 
-    // Nested <input>-inside-contenteditable. Only the contenteditable is considered.
+    // Nested <input>-inside-contenteditable.
 
     [webView synchronouslyLoadHTMLString:applyStyle(@"<div contenteditable style='width: 100px; height: 100px;'><input type='text' style='width: 50px; height: 50px;'></div>")];
     contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
-    EXPECT_EQ(1UL, contexts.count);
-    EXPECT_EQ(CGRectMake(0, 0, 100, 100), contexts[0].boundingRect);
+    EXPECT_EQ(2UL, contexts.count);
+    EXPECT_EQ(CGRectMake(0, 0, 50, 50), contexts[0].boundingRect);
+    EXPECT_EQ(CGRectMake(0, 0, 100, 100), contexts[1].boundingRect);
 }
 
 TEST(WebKit, DISABLED_FocusTextInputContext)
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to