WebKit Bugzilla
New
Browse
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
RESOLVED FIXED
237678
AX: AccessibilityObject::visibleCharacterRange is extremely slow when called on objects with lots of text
https://bugs.webkit.org/show_bug.cgi?id=237678
Summary
AX: AccessibilityObject::visibleCharacterRange is extremely slow when called ...
Tyler Wilcock
Reported
2022-03-09 15:03:07 PST
In this example `sample` with 100 total samplings, visibleCharacterRange accounted for 95% of the runtime: 95 WebCore::AccessibilityObject::visibleCharacterRange() const + 1740 (WebCore + 17852704) [0x10d83a920] 62 WebCore::AccessibilityRenderObject::boundsForRange(WebCore::SimpleRange const&) const + 904 (WebCore + 18023804) [0x10d86457c] 36 WebCore::boundsForRects(WebCore::LayoutRect const&, WebCore::LayoutRect const&, WebCore::SimpleRange const&) + 292 (WebCore + 18022192) [0x10d863f30] 26 WebCore::RenderObject::absoluteTextRects(WebCore::SimpleRange const&, WTF::OptionSet<WebCore::RenderObject::BoundingRectBehavior>) + 360 (WebCore + 33800176) [0x10e76fff0] 25 WebCore::absoluteRectsForRangeInText(WebCore::SimpleRange const&, WebCore::Text&, WTF::OptionSet<WebCore::RenderObject::BoundingRectBehavior>) + 100 (WebCore + 33801512) [0x10e770528] 18 WebCore::RenderText::absoluteQuadsForRange(unsigned int, unsigned int, bool, bool, bool*) const + 1588 (WebCore + 33927852) [0x10e78f2ac] 18 WebCore::RenderObject::localToContainerQuad(WebCore::FloatQuad const&, WebCore::RenderLayerModelObject const*, WTF::OptionSet<WebCore::MapCoordinatesMode>, bool*) const + 292 (WebCore + 33784096) [0x10e76c120]
Attachments
Patch
(26.19 KB, patch)
2022-03-09 15:48 PST
,
Tyler Wilcock
no flags
Details
Formatted Diff
Diff
Patch
(30.83 KB, patch)
2022-03-16 15:24 PDT
,
Tyler Wilcock
no flags
Details
Formatted Diff
Diff
Patch
(73.79 KB, patch)
2022-03-18 15:03 PDT
,
Tyler Wilcock
no flags
Details
Formatted Diff
Diff
Patch
(73.80 KB, patch)
2022-03-18 15:06 PDT
,
Tyler Wilcock
no flags
Details
Formatted Diff
Diff
Patch
(73.47 KB, patch)
2022-03-21 09:13 PDT
,
Tyler Wilcock
no flags
Details
Formatted Diff
Diff
Show Obsolete
(4)
View All
Add attachment
proposed patch, testcase, etc.
Radar WebKit Bug Importer
Comment 1
2022-03-09 15:03:17 PST
<
rdar://problem/90059576
>
Tyler Wilcock
Comment 2
2022-03-09 15:48:48 PST
Created
attachment 454296
[details]
Patch
Andres Gonzalez
Comment 3
2022-03-10 06:30:11 PST
(In reply to Tyler Wilcock from
comment #2
)
> Created
attachment 454296
[details]
> Patch
--- a/Source/WebCore/accessibility/AccessibilityObject.cpp +++ a/Source/WebCore/accessibility/AccessibilityObject.cpp +Vector<BoundaryPoint> AccessibilityObject::previousLineStartBoundaryPoints(const VisiblePosition& startingPosition, const SimpleRange& targetRange, unsigned positionsToRetrieve) const { ... + VisiblePosition lastPosition = startingPosition; + for (unsigned i = 0; i < positionsToRetrieve; i++) { + auto newPosition = previousLineStartPositionInternal(lastPosition); Can't we just have one local std::optional<VisiblePosition> instead of lastPosition and newPosition? + auto boundaryPoint = makeBoundaryPoint(*newPosition); + if (!boundaryPoint || !contains(targetRange, *boundaryPoint)) + break; I'd think that if !contains(...) you want to continue instead of breaking out of the loop, because another unvisited position may be inside the range. +std::optional<BoundaryPoint> AccessibilityObject::lastBoundaryPointContainedInRect(const Vector<BoundaryPoint>& boundaryPoints, const BoundaryPoint& startBoundary, FloatRect rect, int leftIndex, int rightIndex) const Pass the rect by ref, const FloatRect&. + auto indexIsValid = [&] (int index) { + return index >= 0 && index < static_cast<int>(boundaryPoints.size()); + }; Doesn't casting a large size_t to int turn into a negative number? I'm pretty sure there is a safer way of doing this conversion, if you actually need to use int. + auto boundaryPointContainedByRect = [&, this] (const BoundaryPoint& testEndBoundary) { Doesn't [&] capture this as well? testEndBoundary -> boundary, testEnd doesn't mean anything here. + if (leftIndex > rightIndex || boundaryPoints.isEmpty()) return std::nullopt; This param check, early return should be done at the beginning of the function. + int midIndex = leftIndex + ((rightIndex - leftIndex) / 2); You can get rid of one ( ). +bool AccessibilityObject::boundaryPointsContainedByRect(const BoundaryPoint& startBoundary, const BoundaryPoint& endBoundary, FloatRect rect) const const FloatRect& --- a/Source/WebCore/accessibility/AccessibilityObject.h +++ a/Source/WebCore/accessibility/AccessibilityObject.h + bool boundaryPointsContainedByRect(const BoundaryPoint&, const BoundaryPoint&, FloatRect) const; + std::optional<BoundaryPoint> lastBoundaryPointContainedInRect(const Vector<BoundaryPoint>&, const BoundaryPoint&, FloatRect, int, int) const; + std::optional<BoundaryPoint> lastBoundaryPointContainedInRect(const Vector<BoundaryPoint>& boundaryPoints, const BoundaryPoint& pairBoundaryPoint, FloatRect targetRect) const We have names "ContainedByRect" and "ContainedInRect". Is there a distinction? Not sure what is more correct grammatically, but we should choose one if possible. The header inlines shouldn't be in the class declaration, but instead put them below the class as inline AccessibilityObject::xxx.
Tyler Wilcock
Comment 4
2022-03-16 15:24:16 PDT
Created
attachment 454901
[details]
Patch
Tyler Wilcock
Comment 5
2022-03-16 15:38:55 PDT
> + auto boundaryPoint = makeBoundaryPoint(*newPosition); > + if (!boundaryPoint || !contains(targetRange, *boundaryPoint)) > + break; > > I'd think that if !contains(...) you want to continue instead of breaking > out of the loop, because another unvisited position may be inside the range.
I don't believe this is true, as any later elements will be further and further outside the range, which allows us to break here. Addressed all of your other comments. Thanks for the review!
Tyler Wilcock
Comment 6
2022-03-18 15:03:14 PDT
Created
attachment 455139
[details]
Patch
Tyler Wilcock
Comment 7
2022-03-18 15:06:58 PDT
Created
attachment 455140
[details]
Patch
Andres Gonzalez
Comment 8
2022-03-21 07:05:08 PDT
(In reply to Tyler Wilcock from
comment #7
)
> Created
attachment 455140
[details]
> Patch
--- a/Source/WebCore/accessibility/AccessibilityObject.cpp +++ a/Source/WebCore/accessibility/AccessibilityObject.cpp +std::optional<BoundaryPoint> AccessibilityObject::lastBoundaryPointContainedInRect(const Vector<BoundaryPoint>& boundaryPoints, const BoundaryPoint& startBoundary, const FloatRect& rect, int leftIndex, int rightIndex) const +{ ... + int midIndex = leftIndex + (rightIndex - leftIndex) / 2; int midIndex = (leftIndex + rightIndex) / 2; --- a/Source/WebCore/accessibility/AccessibilityObject.h +++ a/Source/WebCore/accessibility/AccessibilityObject.h +#if ENABLE(ACCESSIBILITY) ... +#endif + #if !ENABLE(ACCESSIBILITY) ... Change to #if ENABLE(ACCESSIBILITY) ... #else ... +inline VisiblePosition AccessibilityObject::previousLineStartPosition(const VisiblePosition& position) const { return VisiblePosition(); } return { }
Andres Gonzalez
Comment 9
2022-03-21 07:18:32 PDT
Can we make the caching in a way that would work for ITM as well?
Tyler Wilcock
Comment 10
2022-03-21 09:12:00 PDT
> --- a/Source/WebCore/accessibility/AccessibilityObject.cpp > +++ a/Source/WebCore/accessibility/AccessibilityObject.cpp > > +std::optional<BoundaryPoint> > AccessibilityObject::lastBoundaryPointContainedInRect(const > Vector<BoundaryPoint>& boundaryPoints, const BoundaryPoint& startBoundary, > const FloatRect& rect, int leftIndex, int rightIndex) const > +{ > ... > + int midIndex = leftIndex + (rightIndex - leftIndex) / 2; > > int midIndex = (leftIndex + rightIndex) / 2;
I agree that your suggestion is the more intuitive, but that approach is vulnerable to overflow if leftIndex + rightIndex is greater than the max int value, while the algorithm in the patch is not vulnerable to this overflow.
> Can we make the caching in a way that would work for ITM as well?
ITM does benefit from the caching in the patch as-is: 1. ITM requests visibleCharacterRange, goes to main-thread live object to get it 2. The live object does the work and caches the value 3. On subsequent requests, if inputs haven't changed, ITM still pays the cost to go to the mainthread but gets the already cached value In order to save a trip to the mainthread to get the cached value, we would need to make the visibleCharacterRange inputs available off the mainthread: 1. AccessibilityObject::elementRange() 2. AccessibilityObject::unobscuredContentRect() 3. AccessibilityObject::elementRect() Not sure there's an easy way to do that, since these functions rely on a lot of data only available on the mainthread (at least right now).
Tyler Wilcock
Comment 11
2022-03-21 09:13:28 PDT
Created
attachment 455251
[details]
Patch
Tyler Wilcock
Comment 12
2022-03-21 09:13:56 PDT
Applied your other suggestions.
EWS
Comment 13
2022-03-21 20:50:59 PDT
Committed
r291600
(
248693@main
): <
https://commits.webkit.org/248693@main
> All reviewed patches have been landed. Closing bug and clearing flags on
attachment 455251
[details]
.
Note
You need to
log in
before you can comment on or make changes to this bug.
Top of Page
Format For Printing
XML
Clone This Bug