Title: [291600] trunk
Revision
291600
Author
tyle...@apple.com
Date
2022-03-21 20:49:48 -0700 (Mon, 21 Mar 2022)

Log Message

AX: AccessibilityObject::visibleCharacterRange is extremely slow when called on objects with lots of text
https://bugs.webkit.org/show_bug.cgi?id=237678

Reviewed by Andres Gonzalez.

Source/WebCore:

AccessibilityObject::visibleCharacterRange is extremely slow when
called on objects with lots of text. For example, trying to enter a
large contenteditable element with VoiceOver causes "Safari not
responding" because WebKit is so slow to return this data.

This patch fixes this in two ways. First, we optimize computation of
the end boundary point by grabbing previous line start positions in
batches and binary searching within each batch to find the correct
value.

I tried to apply this algorithm to the computation of the start
boundary, but that regressed performance for small and medium text
objects, and didn't yield any noticeable improvement for large text
objects. Keeping start boundary computation as-is while changing the
end boundary computation provided the best performance at all text
sizes.

Second, this patch caches visibleCharacterRange results, as the same
inputs to this function will always yield the same output. It's common
for this data to be requested multiple times without any change in
page state (e.g. scrolling), so caching further improves performance
by a lot.

Additional testcases added to accessibility/visible-character-range.html to
ensure behavior is correct.

* accessibility/AccessibilityObject.cpp:
(WebCore::AccessibilityObject::previousLineStartBoundaryPoints const): Added.
(WebCore::AccessibilityObject::lastBoundaryPointContainedInRect const): Added.
(WebCore::AccessibilityObject::boundaryPointsContainedInRect const): Added.
(WebCore::AccessibilityObject::visibleCharacterRange const):
Wraps visibleCharacterRangeInternal to handle caching.
(WebCore::AccessibilityObject::visibleCharacterRangeInternal const):
(WebCore::AccessibilityObject::previousLineStartPositionInternal const): Added.
(WebCore::AccessibilityObject::previousLineStartPosition const):
Wraps previousLineStartPositionInternal to return default
VisualPosition if it returns std::nullopt (existing callers expect this behavior)
* accessibility/AccessibilityObject.h:
(WebCore::AccessibilityObject::lastBoundaryPointContainedInRect const): Added.

* dom/BoundaryPoint.cpp:
(WebCore::operator<<):
* dom/BoundaryPoint.h:
Added implementation of operator<< to make debugging boundary points easier.

LayoutTests:

Add many new visible character range testcases split across four new tests.

* accessibility/visible-character-range-basic.html: Added.
* accessibility/visible-character-range-height-changes.html: Added.
* accessibility/visible-character-range-scrolling.html: Added.
* accessibility/visible-character-range-width-changes.html: Added.
* platform/glib/TestExpectations: Skip new tests.
* platform/ios-simulator-wk2/TestExpectations:
Mark new tests as crashing since the old test was crashing in `main`.
* platform/ios/TestExpectations: Enable new tests.
* platform/ios/accessibility/visible-character-range-basic-expected.txt: Added.
* platform/ios/accessibility/visible-character-range-expected.txt: Removed.
* platform/ios/accessibility/visible-character-range-height-changes-expected.txt: Added.
* platform/ios/accessibility/visible-character-range-scrolling-expected.txt: Added.
* platform/ios/accessibility/visible-character-range-width-changes-expected.txt: Added.
* platform/mac-wk1/TestExpectations: Skip new tests.
* platform/mac/accessibility/visible-character-range-basic-expected.txt: Added.
* platform/mac/accessibility/visible-character-range-expected.txt: Removed.
* platform/mac/accessibility/visible-character-range-height-changes-expected.txt: Added.
* platform/mac/accessibility/visible-character-range-scrolling-expected.txt: Added.
* platform/mac/accessibility/visible-character-range-width-changes-expected.txt: Added.
* platform/win/TestExpectations: Skip new tests.
* resources/accessibility-helper.js:
(visibleRange): Added.

Modified Paths

Added Paths

Removed Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (291599 => 291600)


--- trunk/LayoutTests/ChangeLog	2022-03-22 03:33:18 UTC (rev 291599)
+++ trunk/LayoutTests/ChangeLog	2022-03-22 03:49:48 UTC (rev 291600)
@@ -1,3 +1,35 @@
+2022-03-21  Tyler Wilcock  <tyle...@apple.com>
+
+        AX: AccessibilityObject::visibleCharacterRange is extremely slow when called on objects with lots of text
+        https://bugs.webkit.org/show_bug.cgi?id=237678
+
+        Reviewed by Andres Gonzalez.
+
+        Add many new visible character range testcases split across four new tests.
+
+        * accessibility/visible-character-range-basic.html: Added.
+        * accessibility/visible-character-range-height-changes.html: Added.
+        * accessibility/visible-character-range-scrolling.html: Added.
+        * accessibility/visible-character-range-width-changes.html: Added.
+        * platform/glib/TestExpectations: Skip new tests.
+        * platform/ios-simulator-wk2/TestExpectations:
+        Mark new tests as crashing since the old test was crashing in `main`.
+        * platform/ios/TestExpectations: Enable new tests.
+        * platform/ios/accessibility/visible-character-range-basic-expected.txt: Added.
+        * platform/ios/accessibility/visible-character-range-expected.txt: Removed.
+        * platform/ios/accessibility/visible-character-range-height-changes-expected.txt: Added.
+        * platform/ios/accessibility/visible-character-range-scrolling-expected.txt: Added.
+        * platform/ios/accessibility/visible-character-range-width-changes-expected.txt: Added.
+        * platform/mac-wk1/TestExpectations: Skip new tests.
+        * platform/mac/accessibility/visible-character-range-basic-expected.txt: Added.
+        * platform/mac/accessibility/visible-character-range-expected.txt: Removed.
+        * platform/mac/accessibility/visible-character-range-height-changes-expected.txt: Added.
+        * platform/mac/accessibility/visible-character-range-scrolling-expected.txt: Added.
+        * platform/mac/accessibility/visible-character-range-width-changes-expected.txt: Added.
+        * platform/win/TestExpectations: Skip new tests.
+        * resources/accessibility-helper.js:
+        (visibleRange): Added.
+
 2022-03-21  Matteo Flores  <matteo_flo...@apple.com>
 
         [ iOS iPhone 12 ] fast/hidpi & fast/layers/hidpi tests are flaky text/image failing

Copied: trunk/LayoutTests/accessibility/visible-character-range-basic.html (from rev 291599, trunk/LayoutTests/accessibility/visible-character-range.html) (0 => 291600)


--- trunk/LayoutTests/accessibility/visible-character-range-basic.html	                        (rev 0)
+++ trunk/LayoutTests/accessibility/visible-character-range-basic.html	2022-03-22 03:49:48 UTC (rev 291600)
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+
+<div id="textless-div" role="group">
+    <input type="text" />
+</div>
+
+<div id="text-div">
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+</div>
+
+<script>
+    var testOutput = "This tests that visibleCharacterRange returns expected visible ranges.\n\n";
+
+    if (window.accessibilityController) {
+        const textlessDiv = accessibilityController.accessibleElementById("textless-div") 
+        testOutput += `Range of text-less div: ${textlessDiv.stringDescriptionOfAttributeValue("AXVisibleCharacterRange")}\n`;
+
+        const text = accessibilityController.accessibleElementById("text-div").childAtIndex(0);
+        testOutput += `Range of text div with default view size: ${text.stringDescriptionOfAttributeValue("AXVisibleCharacterRange")}\n`;
+
+        testOutput += visibleRange(text, {width: 0, height: 500, scrollTop: 0});
+        testOutput += visibleRange(text, {width: 500, height: 0, scrollTop: 0});
+        testOutput += visibleRange(text, {width: 1, height: 500, scrollTop: 0});
+        testOutput += visibleRange(text, {width: 500, height: 1, scrollTop: 0});
+        testOutput += visibleRange(text, {width: 80, height: 80, scrollTop: 0});
+        testOutput += visibleRange(text, {width: 500, height: 200, scrollTop: 0});
+
+        debug(testOutput);
+        document.getElementById("text-div").style.visibility = "hidden";
+    }
+</script>
+
+</body>
+</html>

Copied: trunk/LayoutTests/accessibility/visible-character-range-height-changes.html (from rev 291599, trunk/LayoutTests/accessibility/visible-character-range.html) (0 => 291600)


--- trunk/LayoutTests/accessibility/visible-character-range-height-changes.html	                        (rev 0)
+++ trunk/LayoutTests/accessibility/visible-character-range-height-changes.html	2022-03-22 03:49:48 UTC (rev 291600)
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+
+<div id="text-div">
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+</div>
+
+<script>
+    var testOutput = "This tests that visibleCharacterRange returns expected visible ranges with various view height values.\n\n";
+
+    if (window.accessibilityController) {
+        const text = accessibilityController.accessibleElementById("text-div").childAtIndex(0);
+        testOutput += "\nTesting view height values 100 to 1300.\n";
+        for (let i = 0; i < 13; i++)
+            testOutput += visibleRange(text, {width: 500, height: (i + 1) * 100, scrollTop: 500});
+
+        debug(testOutput);
+        document.getElementById("text-div").style.visibility = "hidden";
+    }
+</script>
+
+</body>
+</html>

Copied: trunk/LayoutTests/accessibility/visible-character-range-scrolling.html (from rev 291599, trunk/LayoutTests/accessibility/visible-character-range.html) (0 => 291600)


--- trunk/LayoutTests/accessibility/visible-character-range-scrolling.html	                        (rev 0)
+++ trunk/LayoutTests/accessibility/visible-character-range-scrolling.html	2022-03-22 03:49:48 UTC (rev 291600)
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+
+<div id="text-div">
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+</div>
+
+<script>
+    var testOutput = "This tests that visibleCharacterRange returns expected visible ranges with various scrollTop values.\n\n";
+
+    if (window.accessibilityController) {
+        const text = accessibilityController.accessibleElementById("text-div").childAtIndex(0);
+        testOutput += "\nTesting scrollTop values 0 to 2000.\n";
+        for (let i = 0; i < 21; i++)
+            testOutput += visibleRange(text, {width: 200, height: 500, scrollTop: i * 100});
+
+        debug(testOutput);
+        document.getElementById("text-div").style.visibility = "hidden";
+    }
+
+</script>
+
+</body>
+</html>

Copied: trunk/LayoutTests/accessibility/visible-character-range-width-changes.html (from rev 291599, trunk/LayoutTests/accessibility/visible-character-range.html) (0 => 291600)


--- trunk/LayoutTests/accessibility/visible-character-range-width-changes.html	                        (rev 0)
+++ trunk/LayoutTests/accessibility/visible-character-range-width-changes.html	2022-03-22 03:49:48 UTC (rev 291600)
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+
+<div id="text-div">
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+test test test test test test test test test test test test test test test test test test test test test test test test test test test test
+</div>
+
+<script>
+    var testOutput = "This tests that visibleCharacterRange returns expected visible ranges with various view width values.\n\n";
+
+    if (window.accessibilityController) {
+        const text = accessibilityController.accessibleElementById("text-div").childAtIndex(0);
+        testOutput += "\nTesting view width values 100 to 1300.\n";
+        for (let i = 0; i < 13; i++)
+            testOutput += visibleRange(text, {width: (i + 1) * 100, height: 500, scrollTop: 500});
+
+        debug(testOutput);
+        document.getElementById("text-div").style.visibility = "hidden";
+    }
+</script>
+
+</body>
+</html>

Deleted: trunk/LayoutTests/accessibility/visible-character-range.html (291599 => 291600)


--- trunk/LayoutTests/accessibility/visible-character-range.html	2022-03-22 03:33:18 UTC (rev 291599)
+++ trunk/LayoutTests/accessibility/visible-character-range.html	2022-03-22 03:49:48 UTC (rev 291600)
@@ -1,73 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
-<html>
-<head>
-<script src=""
-</head>
-<body id="body">
-
-<div id="group">
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-test test test test test test test test test test test test test test test test test test test test test test test test test test test test
-</div>
-
-<script>
-
-    description("This tests that visibleCharacterRange returns expected visible ranges while modifying obscured content.");
-
-    if (window.accessibilityController) {
-        var text = accessibilityController.accessibleElementById("group").childAtIndex(0);
-        debug("Visible range: " + text.stringDescriptionOfAttributeValue("AXVisibleCharacterRange"));
-
-        testRunner.setViewSize(500, 200);
-        debug("Visible range (500, 200): " + text.stringDescriptionOfAttributeValue("AXVisibleCharacterRange"));
-
-        document.body.scrollTop = 200;
-        testRunner.setViewSize(200, 500);
-        debug("Visible range, offset: 200 (200, 500): " + text.stringDescriptionOfAttributeValue("AXVisibleCharacterRange"));
-
-        document.getElementById("group").style.visibility = "hidden";
-    }
-
-</script>
-
-</body>
-</html>

Modified: trunk/LayoutTests/platform/glib/TestExpectations (291599 => 291600)


--- trunk/LayoutTests/platform/glib/TestExpectations	2022-03-22 03:33:18 UTC (rev 291599)
+++ trunk/LayoutTests/platform/glib/TestExpectations	2022-03-22 03:49:48 UTC (rev 291600)
@@ -346,6 +346,12 @@
 accessibility/dynamically-changing-iframe-remains-accessible.html [ Skip ]
 accessibility/ignore-modals-without-any-content.html [ Skip ]
 
+# Missing `AccessibilityUIElement::stringDescriptionOfAttributeValue` implementation.
+accessibility/visible-character-range-basic.html [ Skip ]
+accessibility/visible-character-range-height-changes.html [ Skip ]
+accessibility/visible-character-range-scrolling.html [ Skip ]
+accessibility/visible-character-range-width-changes.html [ Skip ]
+
 webkit.org/b/212805 accessibility/svg-text.html [ Failure ]
 
 # Added in r263823. Both tests are timing out.

Modified: trunk/LayoutTests/platform/ios/TestExpectations (291599 => 291600)


--- trunk/LayoutTests/platform/ios/TestExpectations	2022-03-22 03:33:18 UTC (rev 291599)
+++ trunk/LayoutTests/platform/ios/TestExpectations	2022-03-22 03:49:48 UTC (rev 291600)
@@ -2098,7 +2098,10 @@
 accessibility/aria-hidden-display-contents-element.html [ Pass ]
 accessibility/display-contents-element-roles.html [ Pass ]
 accessibility/video-element-url-attribute.html [ Pass ]
-accessibility/visible-character-range.html [ Pass ]
+accessibility/visible-character-range-basic.html [ Pass ]
+accessibility/visible-character-range-height-changes.html [ Pass ]
+accessibility/visible-character-range-scrolling.html [ Pass ]
+accessibility/visible-character-range-width-changes.html [ Pass ]
 accessibility/ancestor-computation.html [ Pass ]
 accessibility/model-element-attributes.html [ Pass ]
 

Added: trunk/LayoutTests/platform/ios/accessibility/visible-character-range-basic-expected.txt (0 => 291600)


--- trunk/LayoutTests/platform/ios/accessibility/visible-character-range-basic-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/platform/ios/accessibility/visible-character-range-basic-expected.txt	2022-03-22 03:49:48 UTC (rev 291600)
@@ -0,0 +1,16 @@
+This tests that visibleCharacterRange returns expected visible ranges.
+
+Range of text-less div: {0, 0}
+Range of text div with default view size: {0, 4200}
+Range with view 0x500, scrollTop 0: {9223372036854775807, 0}
+Range with view 500x0, scrollTop 0: {9223372036854775807, 0}
+Range with view 1x500, scrollTop 0: {9223372036854775807, 0}
+Range with view 500x1, scrollTop 0: {9223372036854775807, 0}
+Range with view 80x80, scrollTop 0: {0, 20}
+Range with view 500x200, scrollTop 0: {0, 720}
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+

Deleted: trunk/LayoutTests/platform/ios/accessibility/visible-character-range-expected.txt (291599 => 291600)


--- trunk/LayoutTests/platform/ios/accessibility/visible-character-range-expected.txt	2022-03-22 03:33:18 UTC (rev 291599)
+++ trunk/LayoutTests/platform/ios/accessibility/visible-character-range-expected.txt	2022-03-22 03:49:48 UTC (rev 291600)
@@ -1,12 +0,0 @@
-This tests that visibleCharacterRange returns expected visible ranges while modifying obscured content.
-
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-
-Visible range: {0, 3900}
-Visible range (500, 200): {0, 270}
-Visible range, offset: 200 (200, 500): {0, 735}
-PASS successfullyParsed is true
-
-TEST COMPLETE
-

Added: trunk/LayoutTests/platform/ios/accessibility/visible-character-range-height-changes-expected.txt (0 => 291600)


--- trunk/LayoutTests/platform/ios/accessibility/visible-character-range-height-changes-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/platform/ios/accessibility/visible-character-range-height-changes-expected.txt	2022-03-22 03:49:48 UTC (rev 291600)
@@ -0,0 +1,22 @@
+This tests that visibleCharacterRange returns expected visible ranges with various view height values.
+
+
+Testing view height values 100 to 1300.
+Range with view 500x100, scrollTop 500: {2250, 360}
+Range with view 500x200, scrollTop 500: {2250, 810}
+Range with view 500x300, scrollTop 500: {2250, 1260}
+Range with view 500x400, scrollTop 500: {2250, 1710}
+Range with view 500x500, scrollTop 500: {2250, 2160}
+Range with view 500x600, scrollTop 500: {2250, 2610}
+Range with view 500x700, scrollTop 500: {2250, 3060}
+Range with view 500x800, scrollTop 500: {2250, 3489}
+Range with view 500x900, scrollTop 500: {2250, 3489}
+Range with view 500x1000, scrollTop 500: {2250, 3489}
+Range with view 500x1100, scrollTop 500: {2250, 3489}
+Range with view 500x1200, scrollTop 500: {2250, 3489}
+Range with view 500x1300, scrollTop 500: {0, 5739}
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/platform/ios/accessibility/visible-character-range-scrolling-expected.txt (0 => 291600)


--- trunk/LayoutTests/platform/ios/accessibility/visible-character-range-scrolling-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/platform/ios/accessibility/visible-character-range-scrolling-expected.txt	2022-03-22 03:49:48 UTC (rev 291600)
@@ -0,0 +1,30 @@
+This tests that visibleCharacterRange returns expected visible ranges with various scrollTop values.
+
+
+Testing scrollTop values 0 to 2000.
+Range with view 200x500, scrollTop 0: {0, 840}
+Range with view 200x500, scrollTop 100: {175, 840}
+Range with view 200x500, scrollTop 200: {350, 840}
+Range with view 200x500, scrollTop 300: {525, 840}
+Range with view 200x500, scrollTop 400: {700, 840}
+Range with view 200x500, scrollTop 500: {875, 840}
+Range with view 200x500, scrollTop 600: {1050, 840}
+Range with view 200x500, scrollTop 700: {1225, 840}
+Range with view 200x500, scrollTop 800: {1400, 840}
+Range with view 200x500, scrollTop 900: {1575, 840}
+Range with view 200x500, scrollTop 1000: {1750, 840}
+Range with view 200x500, scrollTop 1100: {1925, 840}
+Range with view 200x500, scrollTop 1200: {2100, 840}
+Range with view 200x500, scrollTop 1300: {2275, 840}
+Range with view 200x500, scrollTop 1400: {2450, 840}
+Range with view 200x500, scrollTop 1500: {2625, 840}
+Range with view 200x500, scrollTop 1600: {2800, 840}
+Range with view 200x500, scrollTop 1700: {2975, 840}
+Range with view 200x500, scrollTop 1800: {3150, 840}
+Range with view 200x500, scrollTop 1900: {3325, 840}
+Range with view 200x500, scrollTop 2000: {3500, 840}
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/platform/ios/accessibility/visible-character-range-width-changes-expected.txt (0 => 291600)


--- trunk/LayoutTests/platform/ios/accessibility/visible-character-range-width-changes-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/platform/ios/accessibility/visible-character-range-width-changes-expected.txt	2022-03-22 03:49:48 UTC (rev 291600)
@@ -0,0 +1,22 @@
+This tests that visibleCharacterRange returns expected visible ranges with various view width values.
+
+
+Testing view width values 100 to 1300.
+Range with view 100x500, scrollTop 500: {375, 360}
+Range with view 200x500, scrollTop 500: {875, 840}
+Range with view 300x500, scrollTop 500: {1250, 1200}
+Range with view 400x500, scrollTop 500: {1750, 1680}
+Range with view 500x500, scrollTop 500: {2250, 2160}
+Range with view 600x500, scrollTop 500: {2750, 2640}
+Range with view 700x500, scrollTop 500: {2730, 3009}
+Range with view 800x500, scrollTop 500: {2250, 3489}
+Range with view 900x500, scrollTop 500: {1815, 3924}
+Range with view 1000x500, scrollTop 500: {1480, 4259}
+Range with view 1100x500, scrollTop 500: {820, 4919}
+Range with view 1200x500, scrollTop 500: {450, 5289}
+Range with view 1300x500, scrollTop 500: {0, 5739}
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Modified: trunk/LayoutTests/platform/ios-simulator-wk2/TestExpectations (291599 => 291600)


--- trunk/LayoutTests/platform/ios-simulator-wk2/TestExpectations	2022-03-22 03:33:18 UTC (rev 291599)
+++ trunk/LayoutTests/platform/ios-simulator-wk2/TestExpectations	2022-03-22 03:49:48 UTC (rev 291600)
@@ -108,7 +108,10 @@
 
 webkit.org/b/203112 scrollingcoordinator/ios/ui-scroll-fixed.html [ Pass Failure ]
 
-webkit.org/b/237557 [ Debug ] accessibility/visible-character-range.html [ Crash ]
+webkit.org/b/237557 [ Debug ] accessibility/visible-character-range-basic.html [ Crash ]
+webkit.org/b/237557 [ Debug ] accessibility/visible-character-range-height-changes.html [ Crash ]
+webkit.org/b/237557 [ Debug ] accessibility/visible-character-range-scrolling.html [ Crash ]
+webkit.org/b/237557 [ Debug ] accessibility/visible-character-range-width-changes.html [ Crash ]
 
 webkit.org/b/215033 imported/w3c/web-platform-tests/websockets/cookies/third-party-cookie-accepted.https.html [ Pass Failure ]
 

Added: trunk/LayoutTests/platform/mac/accessibility/visible-character-range-basic-expected.txt (0 => 291600)


--- trunk/LayoutTests/platform/mac/accessibility/visible-character-range-basic-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/platform/mac/accessibility/visible-character-range-basic-expected.txt	2022-03-22 03:49:48 UTC (rev 291600)
@@ -0,0 +1,16 @@
+This tests that visibleCharacterRange returns expected visible ranges.
+
+Range of text-less div: NSRange: {0, 0}
+Range of text div with default view size: NSRange: {0, 4495}
+Range with view 0x500, scrollTop 0: NSRange: {9223372036854775807, 0}
+Range with view 500x0, scrollTop 0: NSRange: {9223372036854775807, 0}
+Range with view 1x500, scrollTop 0: NSRange: {9223372036854775807, 0}
+Range with view 500x1, scrollTop 0: NSRange: {9223372036854775807, 0}
+Range with view 80x80, scrollTop 0: NSRange: {0, 10}
+Range with view 500x200, scrollTop 0: NSRange: {0, 810}
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+

Deleted: trunk/LayoutTests/platform/mac/accessibility/visible-character-range-expected.txt (291599 => 291600)


--- trunk/LayoutTests/platform/mac/accessibility/visible-character-range-expected.txt	2022-03-22 03:33:18 UTC (rev 291599)
+++ trunk/LayoutTests/platform/mac/accessibility/visible-character-range-expected.txt	2022-03-22 03:49:48 UTC (rev 291600)
@@ -1,12 +0,0 @@
-This tests that visibleCharacterRange returns expected visible ranges while modifying obscured content.
-
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-
-Visible range: NSRange: {0, 4205}
-Visible range (500, 200): NSRange: {0, 360}
-Visible range, offset: 200 (200, 500): NSRange: {0, 690}
-PASS successfullyParsed is true
-
-TEST COMPLETE
-

Added: trunk/LayoutTests/platform/mac/accessibility/visible-character-range-height-changes-expected.txt (0 => 291600)


--- trunk/LayoutTests/platform/mac/accessibility/visible-character-range-height-changes-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/platform/mac/accessibility/visible-character-range-height-changes-expected.txt	2022-03-22 03:49:48 UTC (rev 291600)
@@ -0,0 +1,22 @@
+This tests that visibleCharacterRange returns expected visible ranges with various view height values.
+
+
+Testing view height values 100 to 1300.
+Range with view 500x100, scrollTop 500: NSRange: {720, 360}
+Range with view 500x200, scrollTop 500: NSRange: {2520, 900}
+Range with view 500x300, scrollTop 500: NSRange: {2520, 1440}
+Range with view 500x400, scrollTop 500: NSRange: {2520, 1890}
+Range with view 500x500, scrollTop 500: NSRange: {2520, 2430}
+Range with view 500x600, scrollTop 500: NSRange: {2520, 2880}
+Range with view 500x700, scrollTop 500: NSRange: {2340, 3399}
+Range with view 500x800, scrollTop 500: NSRange: {1800, 3939}
+Range with view 500x900, scrollTop 500: NSRange: {1350, 4389}
+Range with view 500x1000, scrollTop 500: NSRange: {810, 4929}
+Range with view 500x1100, scrollTop 500: NSRange: {360, 5379}
+Range with view 500x1200, scrollTop 500: NSRange: {0, 5739}
+Range with view 500x1300, scrollTop 500: NSRange: {0, 5739}
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/platform/mac/accessibility/visible-character-range-scrolling-expected.txt (0 => 291600)


--- trunk/LayoutTests/platform/mac/accessibility/visible-character-range-scrolling-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/platform/mac/accessibility/visible-character-range-scrolling-expected.txt	2022-03-22 03:49:48 UTC (rev 291600)
@@ -0,0 +1,30 @@
+This tests that visibleCharacterRange returns expected visible ranges with various scrollTop values.
+
+
+Testing scrollTop values 0 to 2000.
+Range with view 200x500, scrollTop 0: NSRange: {0, 810}
+Range with view 200x500, scrollTop 100: NSRange: {180, 780}
+Range with view 200x500, scrollTop 200: NSRange: {330, 810}
+Range with view 200x500, scrollTop 300: NSRange: {510, 810}
+Range with view 200x500, scrollTop 400: NSRange: {660, 810}
+Range with view 200x500, scrollTop 500: NSRange: {840, 810}
+Range with view 200x500, scrollTop 600: NSRange: {990, 810}
+Range with view 200x500, scrollTop 700: NSRange: {1170, 810}
+Range with view 200x500, scrollTop 800: NSRange: {1320, 810}
+Range with view 200x500, scrollTop 900: NSRange: {1500, 810}
+Range with view 200x500, scrollTop 1000: NSRange: {1680, 780}
+Range with view 200x500, scrollTop 1100: NSRange: {1830, 810}
+Range with view 200x500, scrollTop 1200: NSRange: {2010, 810}
+Range with view 200x500, scrollTop 1300: NSRange: {2160, 810}
+Range with view 200x500, scrollTop 1400: NSRange: {2340, 810}
+Range with view 200x500, scrollTop 1500: NSRange: {2490, 810}
+Range with view 200x500, scrollTop 1600: NSRange: {2670, 810}
+Range with view 200x500, scrollTop 1700: NSRange: {2820, 810}
+Range with view 200x500, scrollTop 1800: NSRange: {3000, 810}
+Range with view 200x500, scrollTop 1900: NSRange: {3180, 780}
+Range with view 200x500, scrollTop 2000: NSRange: {3330, 810}
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/platform/mac/accessibility/visible-character-range-width-changes-expected.txt (0 => 291600)


--- trunk/LayoutTests/platform/mac/accessibility/visible-character-range-width-changes-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/platform/mac/accessibility/visible-character-range-width-changes-expected.txt	2022-03-22 03:49:48 UTC (rev 291600)
@@ -0,0 +1,22 @@
+This tests that visibleCharacterRange returns expected visible ranges with various view width values.
+
+
+Testing view width values 100 to 1300.
+Range with view 100x500, scrollTop 500: NSRange: {80, 260}
+Range with view 200x500, scrollTop 500: NSRange: {840, 810}
+Range with view 300x500, scrollTop 500: NSRange: {1400, 1350}
+Range with view 400x500, scrollTop 500: NSRange: {1960, 1890}
+Range with view 500x500, scrollTop 500: NSRange: {2520, 2430}
+Range with view 600x500, scrollTop 500: NSRange: {2940, 2799}
+Range with view 700x500, scrollTop 500: NSRange: {2375, 3364}
+Range with view 800x500, scrollTop 500: NSRange: {1885, 3854}
+Range with view 900x500, scrollTop 500: NSRange: {1320, 4419}
+Range with view 1000x500, scrollTop 500: NSRange: {925, 4814}
+Range with view 1100x500, scrollTop 500: NSRange: {400, 5339}
+Range with view 1200x500, scrollTop 500: NSRange: {0, 5739}
+Range with view 1300x500, scrollTop 500: NSRange: {0, 5739}
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Modified: trunk/LayoutTests/platform/mac-wk1/TestExpectations (291599 => 291600)


--- trunk/LayoutTests/platform/mac-wk1/TestExpectations	2022-03-22 03:33:18 UTC (rev 291599)
+++ trunk/LayoutTests/platform/mac-wk1/TestExpectations	2022-03-22 03:49:48 UTC (rev 291600)
@@ -858,7 +858,10 @@
 editing/spelling/spelling-unified-emulation.html [ Failure ]
 
 # Skip due to lack of DumpRenderTree `AccessibilityUIElement::stringDescriptionOfAttributeValue` implementation.
-accessibility/visible-character-range.html [ Skip ]
+accessibility/visible-character-range-basic.html [ Skip ]
+accessibility/visible-character-range-height-changes.html [ Skip ]
+accessibility/visible-character-range-scrolling.html [ Skip ]
+accessibility/visible-character-range-width-changes.html [ Skip ]
 accessibility/model-element-attributes.html [ Skip ]
 
 # <rdar://problem/26050923> The result is probably still a pass, but we don't have a way

Modified: trunk/LayoutTests/platform/win/TestExpectations (291599 => 291600)


--- trunk/LayoutTests/platform/win/TestExpectations	2022-03-22 03:33:18 UTC (rev 291599)
+++ trunk/LayoutTests/platform/win/TestExpectations	2022-03-22 03:49:48 UTC (rev 291600)
@@ -465,9 +465,13 @@
 accessibility/listbox-clear-selection.html [ Skip ]
 accessibility/embedded-image-description.html [ Skip ]
 accessibility/img-no-alt-not-ignored-with-title.html [ Skip ]
-accessibility/visible-character-range.html [ Skip ]
 accessibility/model-element-attributes.html [ Skip ]
 
+accessibility/visible-character-range-basic.html [ Skip ]
+accessibility/visible-character-range-height-changes.html [ Skip ]
+accessibility/visible-character-range-scrolling.html [ Skip ]
+accessibility/visible-character-range-width-changes.html [ Skip ]
+
 # Need to implement AccessibilityUIElement::hasDocumentRoleAncestor(), AccessibilityUIElement::hasWebApplicationAncestor(),
 # AccessibilityUIElement::isInDescriptionListDetail(), AccessibilityUIElement::isInDescriptionListTerm(), and
 # AccessibilityUIElement::isInCell().

Modified: trunk/LayoutTests/resources/accessibility-helper.js (291599 => 291600)


--- trunk/LayoutTests/resources/accessibility-helper.js	2022-03-22 03:33:18 UTC (rev 291599)
+++ trunk/LayoutTests/resources/accessibility-helper.js	2022-03-22 03:49:48 UTC (rev 291600)
@@ -51,6 +51,12 @@
     return true;
 }
 
+function visibleRange(axElement, {width, height, scrollTop}) {
+    document.body.scrollTop = scrollTop;
+    testRunner.setViewSize(width, height);
+    return `Range with view ${width}x${height}, scrollTop ${scrollTop}: ${axElement.stringDescriptionOfAttributeValue("AXVisibleCharacterRange")}\n`;
+}
+
 function platformValueForW3CName(accessibilityObject, includeSource=false) {
     var result;
     if (accessibilityController.platformName == "atspi")

Modified: trunk/Source/WebCore/ChangeLog (291599 => 291600)


--- trunk/Source/WebCore/ChangeLog	2022-03-22 03:33:18 UTC (rev 291599)
+++ trunk/Source/WebCore/ChangeLog	2022-03-22 03:49:48 UTC (rev 291600)
@@ -1,3 +1,55 @@
+2022-03-21  Tyler Wilcock  <tyle...@apple.com>
+
+        AX: AccessibilityObject::visibleCharacterRange is extremely slow when called on objects with lots of text
+        https://bugs.webkit.org/show_bug.cgi?id=237678
+
+        Reviewed by Andres Gonzalez.
+
+        AccessibilityObject::visibleCharacterRange is extremely slow when
+        called on objects with lots of text. For example, trying to enter a
+        large contenteditable element with VoiceOver causes "Safari not
+        responding" because WebKit is so slow to return this data.
+
+        This patch fixes this in two ways. First, we optimize computation of
+        the end boundary point by grabbing previous line start positions in
+        batches and binary searching within each batch to find the correct
+        value.
+
+        I tried to apply this algorithm to the computation of the start
+        boundary, but that regressed performance for small and medium text
+        objects, and didn't yield any noticeable improvement for large text
+        objects. Keeping start boundary computation as-is while changing the
+        end boundary computation provided the best performance at all text
+        sizes.
+
+        Second, this patch caches visibleCharacterRange results, as the same
+        inputs to this function will always yield the same output. It's common
+        for this data to be requested multiple times without any change in
+        page state (e.g. scrolling), so caching further improves performance
+        by a lot.
+
+        Additional testcases added to accessibility/visible-character-range.html to
+        ensure behavior is correct.
+
+        * accessibility/AccessibilityObject.cpp:
+        (WebCore::AccessibilityObject::previousLineStartBoundaryPoints const): Added.
+        (WebCore::AccessibilityObject::lastBoundaryPointContainedInRect const): Added.
+        (WebCore::AccessibilityObject::boundaryPointsContainedInRect const): Added.
+        (WebCore::AccessibilityObject::visibleCharacterRange const):
+        Wraps visibleCharacterRangeInternal to handle caching.
+        (WebCore::AccessibilityObject::visibleCharacterRangeInternal const):
+        (WebCore::AccessibilityObject::previousLineStartPositionInternal const): Added.
+        (WebCore::AccessibilityObject::previousLineStartPosition const):
+        Wraps previousLineStartPositionInternal to return default
+        VisualPosition if it returns std::nullopt (existing callers expect this behavior)
+        * accessibility/AccessibilityObject.h:
+        (WebCore::AccessibilityObject::lastBoundaryPointContainedInRect const): Added.
+
+        * dom/BoundaryPoint.cpp:
+        (WebCore::operator<<):
+        * dom/BoundaryPoint.h:
+        Added implementation of operator<< to make debugging boundary points easier.
+
 2022-03-21  Alex Christensen  <achristen...@webkit.org>
 
         Implement CSSNumericValue.mul, div, add, sub, max, and min

Modified: trunk/Source/WebCore/accessibility/AccessibilityObject.cpp (291599 => 291600)


--- trunk/Source/WebCore/accessibility/AccessibilityObject.cpp	2022-03-22 03:33:18 UTC (rev 291599)
+++ trunk/Source/WebCore/accessibility/AccessibilityObject.cpp	2022-03-22 03:49:48 UTC (rev 291600)
@@ -732,50 +732,122 @@
     return AXObjectCache::rangeForNodeContents(*node);
 }
 
+Vector<BoundaryPoint> AccessibilityObject::previousLineStartBoundaryPoints(const VisiblePosition& startingPosition, const SimpleRange& targetRange, unsigned positionsToRetrieve) const
+{
+    Vector<BoundaryPoint> boundaryPoints;
+    boundaryPoints.reserveInitialCapacity(positionsToRetrieve);
+
+    std::optional<VisiblePosition> lastPosition = startingPosition;
+    for (unsigned i = 0; i < positionsToRetrieve; i++) {
+        lastPosition = previousLineStartPositionInternal(*lastPosition);
+        if (!lastPosition)
+            break;
+
+        auto boundaryPoint = makeBoundaryPoint(*lastPosition);
+        if (!boundaryPoint || !contains(targetRange, *boundaryPoint))
+            break;
+
+        boundaryPoints.uncheckedAppend(WTFMove(*boundaryPoint));
+    }
+    boundaryPoints.shrinkToFit();
+    return boundaryPoints;
+}
+
+std::optional<BoundaryPoint> AccessibilityObject::lastBoundaryPointContainedInRect(const Vector<BoundaryPoint>& boundaryPoints, const BoundaryPoint& startBoundary, const FloatRect& rect, int leftIndex, int rightIndex) const
+{
+    if (leftIndex > rightIndex || boundaryPoints.isEmpty())
+        return std::nullopt;
+
+    auto indexIsValid = [&] (int index) {
+        return index >= 0 && static_cast<size_t>(index) < boundaryPoints.size();
+    };
+    auto boundaryPointContainedInRect = [&] (const BoundaryPoint& boundary) {
+        return boundaryPointsContainedInRect(startBoundary, boundary, rect);
+    };
+
+    int midIndex = leftIndex + (rightIndex - leftIndex) / 2;
+    if (boundaryPointContainedInRect(boundaryPoints.at(midIndex))) {
+        // We have a match if `midIndex` boundary point is contained in the rect, but the one at `midIndex - 1` isn't.
+        if (indexIsValid(midIndex - 1) && !boundaryPointContainedInRect(boundaryPoints.at(midIndex - 1)))
+            return boundaryPoints.at(midIndex);
+
+        return lastBoundaryPointContainedInRect(boundaryPoints, startBoundary, rect, leftIndex, midIndex - 1);
+    }
+    // And vice versa, we have a match if the `midIndex` boundary point is not contained in the rect, but the one at `midIndex + 1` is.
+    if (indexIsValid(midIndex + 1) && boundaryPointContainedInRect(boundaryPoints.at(midIndex + 1)))
+        return boundaryPoints.at(midIndex + 1);
+
+    return lastBoundaryPointContainedInRect(boundaryPoints, startBoundary, rect, midIndex + 1, rightIndex);
+}
+
+bool AccessibilityObject::boundaryPointsContainedInRect(const BoundaryPoint& startBoundary, const BoundaryPoint& endBoundary, const FloatRect& rect) const
+{
+    auto elementRect = boundsForRange({ startBoundary, endBoundary });
+    return rect.contains(elementRect.location() + elementRect.size());
+}
+
 std::optional<SimpleRange> AccessibilityObject::visibleCharacterRange() const
 {
     auto range = elementRange();
-    if (!range)
-        return std::nullopt;
-
     auto contentRect = unobscuredContentRect();
     auto elementRect = snappedIntRect(this->elementRect());
-    if (!contentRect.intersects(elementRect))
+    auto inputs = std::make_tuple(range, contentRect, elementRect);
+    if (m_cachedVisibleCharacterRangeInputs && *m_cachedVisibleCharacterRangeInputs == inputs)
+        return m_cachedVisibleCharacterRange;
+
+    auto computedRange = visibleCharacterRangeInternal(range, contentRect, elementRect);
+    m_cachedVisibleCharacterRangeInputs = inputs;
+    m_cachedVisibleCharacterRange = computedRange;
+    return computedRange;
+}
+
+std::optional<SimpleRange> AccessibilityObject::visibleCharacterRangeInternal(const std::optional<SimpleRange>& range, const FloatRect& contentRect, const IntRect& startingElementRect) const
+{
+    if (!range || !contentRect.intersects(startingElementRect))
         return std::nullopt;
 
-    std::optional<BoundaryPoint> startBoundary = range->start;
-    std::optional<BoundaryPoint> endBoundary = range->end;
+    auto elementRect = startingElementRect;
+    auto startBoundary = range->start;
+    auto endBoundary = range->end;
 
     // Origin isn't contained in visible rect, start moving forward by line.
     while (!contentRect.contains(elementRect.location())) {
-        auto nextLinePosition = nextLineEndPosition(VisiblePosition(makeContainerOffsetPosition(*startBoundary)));
-        std::optional<BoundaryPoint> testStartBoundary = makeBoundaryPoint(nextLinePosition);
+        auto nextLinePosition = nextLineEndPosition(VisiblePosition(makeContainerOffsetPosition(startBoundary)));
+        auto testStartBoundary = makeBoundaryPoint(nextLinePosition);
         if (!testStartBoundary || !contains(*range, *testStartBoundary))
             break;
-        
-        startBoundary = testStartBoundary;
-        elementRect = boundsForRange(SimpleRange(*startBoundary, range->end));
+
+        startBoundary = *testStartBoundary;
+        elementRect = boundsForRange(SimpleRange(startBoundary, range->end));
         if (elementRect.isEmpty())
             break;
     }
 
-    // End isn't contained in visible rect, start moving backwards by line.
-    while (!contentRect.contains(elementRect.location() + elementRect.size())) {
-        auto previousLinePosition = previousLineStartPosition(VisiblePosition(makeContainerOffsetPosition(*endBoundary)));
-        std::optional<BoundaryPoint> testEndBoundary = makeBoundaryPoint(previousLinePosition);
-        if (!testEndBoundary || !contains(*range, *testEndBoundary))
+    // Computing previous line start positions is cheap relative to computing boundsForRange, so compute the end boundary by
+    // grabbing batches of lines and binary searching within them to minimize calls to boundsForRange.
+    Vector<BoundaryPoint> boundaryPoints = { endBoundary };
+    do {
+        // If the first boundary point is contained in contentRect, then it's a match because we know everything in the last batch
+        // of lines was not contained in contentRect.
+        if (boundaryPointsContainedInRect(startBoundary, boundaryPoints.at(0), contentRect)) {
+            endBoundary = boundaryPoints.at(0);
             break;
-        
-        endBoundary = testEndBoundary;
-        elementRect = boundsForRange({ *startBoundary, *endBoundary });
+        }
+
+        auto lastBoundaryPoint = boundaryPoints.last();
+        elementRect = boundsForRange({ startBoundary, lastBoundaryPoint });
         if (elementRect.isEmpty())
             break;
-    }
+        // Otherwise if the last boundary point is contained in contentRect, then we know some boundary point in this batch is
+        // our target end boundary point.
+        if (contentRect.contains(elementRect.location() + elementRect.size())) {
+            endBoundary = lastBoundaryPointContainedInRect(boundaryPoints, startBoundary, contentRect).value_or(lastBoundaryPoint);
+            break;
+        }
+        boundaryPoints = previousLineStartBoundaryPoints(VisiblePosition(makeContainerOffsetPosition(lastBoundaryPoint)), *range, 64);
+    } while (!boundaryPoints.isEmpty());
 
-    if (!startBoundary || !endBoundary)
-        return std::nullopt;
-    
-    return {{ *startBoundary, *endBoundary }};
+    return { { startBoundary, endBoundary } };
 }
 
 std::optional<SimpleRange> AccessibilityObject::findTextRange(const Vector<String>& searchStrings, const SimpleRange& start, AccessibilitySearchTextDirection direction) const
@@ -1547,24 +1619,23 @@
     return endPosition;
 }
 
-VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& visiblePos) const
+std::optional<VisiblePosition> AccessibilityObject::previousLineStartPositionInternal(const VisiblePosition& visiblePosition) const
 {
-    if (visiblePos.isNull())
-        return VisiblePosition();
+    if (visiblePosition.isNull())
+        return std::nullopt;
 
-    // make sure we move off of a line start
-    VisiblePosition prevVisiblePos = visiblePos.previous();
-    if (prevVisiblePos.isNull())
-        return VisiblePosition();
+    // Make sure we move off of a line start.
+    auto previousVisiblePosition = visiblePosition.previous();
+    if (previousVisiblePosition.isNull())
+        return std::nullopt;
 
-    VisiblePosition startPosition = startOfLine(prevVisiblePos);
-
-    // as long as the position hasn't reached the beginning of the doc,  keep searching for a valid line start position
-    // There are cases like when the position is next to a floating object that'll return null for start of line. This code will avoid returning null.
+    auto startPosition = startOfLine(previousVisiblePosition);
+    // As long as the position hasn't reached the beginning of the document, keep searching for a valid line start position.
+    // This avoids returning a null position when we shouldn't, like when a position is next to a floating object.
     if (startPosition.isNull()) {
-        while (startPosition.isNull() && prevVisiblePos.isNotNull()) {
-            prevVisiblePos = prevVisiblePos.previous();
-            startPosition = startOfLine(prevVisiblePos);
+        while (startPosition.isNull() && previousVisiblePosition.isNotNull()) {
+            previousVisiblePosition = previousVisiblePosition.previous();
+            startPosition = startOfLine(previousVisiblePosition);
         }
     } else
         startPosition = updateAXLineStartForVisiblePosition(startPosition);

Modified: trunk/Source/WebCore/accessibility/AccessibilityObject.h (291599 => 291600)


--- trunk/Source/WebCore/accessibility/AccessibilityObject.h	2022-03-22 03:33:18 UTC (rev 291599)
+++ trunk/Source/WebCore/accessibility/AccessibilityObject.h	2022-03-22 03:49:48 UTC (rev 291600)
@@ -829,6 +829,12 @@
     std::optional<SimpleRange> selectionRange() const;
     std::optional<SimpleRange> findTextRange(const Vector<String>& searchStrings, const SimpleRange& start, AccessibilitySearchTextDirection) const;
     std::optional<SimpleRange> visibleCharacterRange() const override;
+    std::optional<SimpleRange> visibleCharacterRangeInternal(const std::optional<SimpleRange>&, const FloatRect&, const IntRect&) const;
+    Vector<BoundaryPoint> previousLineStartBoundaryPoints(const VisiblePosition&, const SimpleRange&, unsigned) const;
+    std::optional<VisiblePosition> previousLineStartPositionInternal(const VisiblePosition&) const;
+    bool boundaryPointsContainedInRect(const BoundaryPoint&, const BoundaryPoint&, const FloatRect&) const;
+    std::optional<BoundaryPoint> lastBoundaryPointContainedInRect(const Vector<BoundaryPoint>&, const BoundaryPoint&, const FloatRect&, int, int) const;
+    std::optional<BoundaryPoint> lastBoundaryPointContainedInRect(const Vector<BoundaryPoint>& boundaryPoints, const BoundaryPoint& startBoundaryPoint, const FloatRect& targetRect) const;
 
     void ariaTreeRows(AccessibilityChildrenVector& rows, AccessibilityChildrenVector& ancestors);
     
@@ -845,6 +851,10 @@
     AXID m_id;
     OptionSet<AXAncestorFlag> m_ancestorFlags;
     AccessibilityObjectInclusion m_lastKnownIsIgnoredValue { AccessibilityObjectInclusion::DefaultBehavior };
+    // std::nullopt is a valid cached value if this object has no visible characters.
+    mutable std::optional<SimpleRange> m_cachedVisibleCharacterRange;
+    // This is std::nullopt if we haven't cached any input yet.
+    mutable std::optional<std::tuple<std::optional<SimpleRange>, FloatRect, IntRect>> m_cachedVisibleCharacterRangeInputs;
 protected: // FIXME: Make the data members private.
     // FIXME: This can be replaced by AXAncestorFlags.
     AccessibilityIsIgnoredFromParentData m_isIgnoredFromParentData;
@@ -852,6 +862,21 @@
     bool m_subtreeDirty { false };
 };
 
+#if ENABLE(ACCESSIBILITY)
+inline std::optional<BoundaryPoint> AccessibilityObject::lastBoundaryPointContainedInRect(const Vector<BoundaryPoint>& boundaryPoints, const BoundaryPoint& startBoundaryPoint, const FloatRect& targetRect) const
+{
+    return lastBoundaryPointContainedInRect(boundaryPoints, startBoundaryPoint, targetRect, 0, boundaryPoints.size() - 1);
+}
+
+inline VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& position) const
+{
+    return previousLineStartPositionInternal(position).value_or(VisiblePosition());
+}
+#else
+inline std::optional<BoundaryPoint> AccessibilityObject::lastBoundaryPointContainedInRect(const Vector<BoundaryPoint>&, const BoundaryPoint&, const FloatRect&) const { return std::nullopt; }
+inline VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& position) const { return { }; }
+#endif
+
 #if !ENABLE(ACCESSIBILITY)
 inline const AccessibilityObject::AccessibilityChildrenVector& AccessibilityObject::children(bool) { return m_children; }
 inline String AccessibilityObject::localizedActionVerb() const { return emptyString(); }

Modified: trunk/Source/WebCore/dom/BoundaryPoint.cpp (291599 => 291600)


--- trunk/Source/WebCore/dom/BoundaryPoint.cpp	2022-03-22 03:33:18 UTC (rev 291599)
+++ trunk/Source/WebCore/dom/BoundaryPoint.cpp	2022-03-22 03:49:48 UTC (rev 291600)
@@ -111,4 +111,13 @@
     return PartialOrdering::unordered;
 }
 
+TextStream& operator<<(TextStream& stream, const BoundaryPoint& boundaryPoint)
+{
+    TextStream::GroupScope scope(stream);
+    stream << "BoundaryPoint ";
+    stream.dumpProperty("node", boundaryPoint.container->debugDescription());
+    stream.dumpProperty("offset", boundaryPoint.offset);
+    return stream;
 }
+
+}

Modified: trunk/Source/WebCore/dom/BoundaryPoint.h (291599 => 291600)


--- trunk/Source/WebCore/dom/BoundaryPoint.h	2022-03-22 03:33:18 UTC (rev 291599)
+++ trunk/Source/WebCore/dom/BoundaryPoint.h	2022-03-22 03:49:48 UTC (rev 291600)
@@ -41,6 +41,8 @@
 bool operator==(const BoundaryPoint&, const BoundaryPoint&);
 bool operator!=(const BoundaryPoint&, const BoundaryPoint&);
 
+WTF::TextStream& operator<<(WTF::TextStream&, const BoundaryPoint&);
+
 template<TreeType = Tree> PartialOrdering treeOrder(const BoundaryPoint&, const BoundaryPoint&);
 
 WEBCORE_EXPORT std::optional<BoundaryPoint> makeBoundaryPointBeforeNode(Node&);
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to