Bug 265434

Summary: AX: TextInputMarkedTextMarkerRange should be cached during initialization
Product: WebKit Reporter: Joshua Hoffman <jhoffman23>
Component: AccessibilityAssignee: Joshua Hoffman <jhoffman23>
Status: RESOLVED FIXED    
Severity: Normal CC: aboxhall, andresg_22, apinheiro, cfleizach, dmazzoni, ews-watchlist, jcraig, samuel_white, tyler_w, webkit-bug-importer
Priority: P2 Keywords: InRadar
Version: WebKit Nightly Build   
Hardware: All   
OS: All   
Attachments:
Description Flags
Patch
none
Patch
none
Patch none

Description Joshua Hoffman 2023-11-27 22:32:07 PST
We do not cache TextInputMarkedTextMarkerRange when isolated objects are initialized. This becomes an issue when an object is initialized while there is already marked text.
Comment 1 Radar WebKit Bug Importer 2023-11-27 22:32:20 PST
<rdar://problem/118866640>
Comment 2 Joshua Hoffman 2023-11-27 22:39:28 PST
Created attachment 468781 [details]
Patch
Comment 3 Tyler Wilcock 2023-11-28 00:09:49 PST
Comment on attachment 468781 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=468781&action=review

> LayoutTests/accessibility/mac/text-input-marked-range.html:27
> +    })

For consistency, maybe we add a semicolon here?

> LayoutTests/accessibility/mac/text-input-marked-text-marker-range.html:25
> +        await waitFor(() => {
> +            text = element.stringForTextMarkerRange(element.textInputMarkedTextMarkerRange());
> +            return text == "test";
> +        });
> +        output += await expectAsync("text", "'test'");

Can this be shortened to:

output += await expectAsync("element.stringForTextMarkerRange(element.textInputMarkedTextMarkerRange())", "test");

> LayoutTests/accessibility/mac/text-input-marked-text-marker-range.html:32
> +        await waitFor(() => {
> +            text = element.stringForTextMarkerRange(element.textInputMarkedTextMarkerRange());
> +            return !text;
> +        });
> +        output += await expectAsync("!text", "true");

Can this be shortened to:

output += await expectAsync("!element.stringForTextMarkerRange(element.textInputMarkedTextMarkerRange())", "true");

> LayoutTests/accessibility/mac/text-input-marked-text-marker-range.html:40
> +        textInputController.setMarkedText(" message", 4, " message".length);
> +        await waitFor(() => {
> +            text = element.stringForTextMarkerRange(element.textInputMarkedTextMarkerRange());
> +            return text == " message";
> +        });
> +        output += expect("text", "' message'");

Can we use await expectAsync here too?

> LayoutTests/accessibility/mac/text-input-marked-text-marker-range.html:45
> +    })

For consistency, maybe we add a semicolon after this parentheses?
Comment 4 Joshua Hoffman 2023-11-28 08:53:37 PST
Created attachment 468785 [details]
Patch
Comment 5 Andres Gonzalez 2023-11-28 10:11:47 PST
(In reply to Joshua Hoffman from comment #4)
> Created attachment 468785 [details]
> Patch

diff --git a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
index 37c619db01e2..8a278862b697 100644
--- a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
+++ b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
@@ -344,9 +344,17 @@ void AXIsolatedObject::initializeProperties(const Ref<AccessibilityObject>& axOb
     if (descriptor.length())
         setProperty(AXPropertyName::ExtendedDescription, descriptor.isolatedCopy());

-    if (object.isTextControl())
+    if (object.isTextControl()) {
         setProperty(AXPropertyName::SelectedTextRange, object.selectedTextRange());

+        std::pair<AXID, CharacterRange> value;
+        auto range = object.textInputMarkedTextMarkerRange();
+        if (auto characterRange = range.characterRange(); range && characterRange)
+            value = { range.start().objectID(), *characterRange };
+
+        setProperty(AXPropertyName::TextInputMarkedTextMarkerRange, value);

AG: do we have to store the empty value for this property or we can apply the sparse property map technique?

+    }
+
     // These properties are only needed on the AXCoreObject interface due to their use in ATSPI,
     // so only cache them for ATSPI.
 #if PLATFORM(ATSPI)
Comment 6 Joshua Hoffman 2023-11-28 10:13:44 PST
(In reply to Andres Gonzalez from comment #5)
> (In reply to Joshua Hoffman from comment #4)
> > Created attachment 468785 [details]
> > Patch
> 
> diff --git a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
> b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
> index 37c619db01e2..8a278862b697 100644
> --- a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
> +++ b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
> @@ -344,9 +344,17 @@ void AXIsolatedObject::initializeProperties(const
> Ref<AccessibilityObject>& axOb
>      if (descriptor.length())
>          setProperty(AXPropertyName::ExtendedDescription,
> descriptor.isolatedCopy());
> 
> -    if (object.isTextControl())
> +    if (object.isTextControl()) {
>          setProperty(AXPropertyName::SelectedTextRange,
> object.selectedTextRange());
> 
> +        std::pair<AXID, CharacterRange> value;
> +        auto range = object.textInputMarkedTextMarkerRange();
> +        if (auto characterRange = range.characterRange(); range &&
> characterRange)
> +            value = { range.start().objectID(), *characterRange };
> +
> +        setProperty(AXPropertyName::TextInputMarkedTextMarkerRange, value);
> 
> AG: do we have to store the empty value for this property or we can apply
> the sparse property map technique?

setProperty handles filtering out default values, so this is already covered!
Comment 7 Andres Gonzalez 2023-11-28 10:25:15 PST
(In reply to Joshua Hoffman from comment #6)
> (In reply to Andres Gonzalez from comment #5)
> > (In reply to Joshua Hoffman from comment #4)
> > > Created attachment 468785 [details]
> > > Patch
> > 
> > diff --git a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
> > b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
> > index 37c619db01e2..8a278862b697 100644
> > --- a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
> > +++ b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
> > @@ -344,9 +344,17 @@ void AXIsolatedObject::initializeProperties(const
> > Ref<AccessibilityObject>& axOb
> >      if (descriptor.length())
> >          setProperty(AXPropertyName::ExtendedDescription,
> > descriptor.isolatedCopy());
> > 
> > -    if (object.isTextControl())
> > +    if (object.isTextControl()) {
> >          setProperty(AXPropertyName::SelectedTextRange,
> > object.selectedTextRange());
> > 
> > +        std::pair<AXID, CharacterRange> value;
> > +        auto range = object.textInputMarkedTextMarkerRange();
> > +        if (auto characterRange = range.characterRange(); range &&
> > characterRange)
> > +            value = { range.start().objectID(), *characterRange };
> > +
> > +        setProperty(AXPropertyName::TextInputMarkedTextMarkerRange, value);
> > 
> > AG: do we have to store the empty value for this property or we can apply
> > the sparse property map technique?
> 
> setProperty handles filtering out default values, so this is already covered!

I think that you can then do:

+        if (auto characterRange = range.characterRange(); range && characterRange)
+            setProperty(AXPropertyName::TextInputMarkedTextMarkerRange, { range.start().objectID(), *characterRange });
+    }

and save the copy to a temp.
Comment 8 Joshua Hoffman 2023-11-28 11:12:39 PST
(In reply to Andres Gonzalez from comment #7)
> (In reply to Joshua Hoffman from comment #6)
> > (In reply to Andres Gonzalez from comment #5)
> > > (In reply to Joshua Hoffman from comment #4)
> > > > Created attachment 468785 [details]
> > > > Patch
> > > 
> > > diff --git a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
> > > b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
> > > index 37c619db01e2..8a278862b697 100644
> > > --- a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
> > > +++ b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
> > > @@ -344,9 +344,17 @@ void AXIsolatedObject::initializeProperties(const
> > > Ref<AccessibilityObject>& axOb
> > >      if (descriptor.length())
> > >          setProperty(AXPropertyName::ExtendedDescription,
> > > descriptor.isolatedCopy());
> > > 
> > > -    if (object.isTextControl())
> > > +    if (object.isTextControl()) {
> > >          setProperty(AXPropertyName::SelectedTextRange,
> > > object.selectedTextRange());
> > > 
> > > +        std::pair<AXID, CharacterRange> value;
> > > +        auto range = object.textInputMarkedTextMarkerRange();
> > > +        if (auto characterRange = range.characterRange(); range &&
> > > characterRange)
> > > +            value = { range.start().objectID(), *characterRange };
> > > +
> > > +        setProperty(AXPropertyName::TextInputMarkedTextMarkerRange, value);
> > > 
> > > AG: do we have to store the empty value for this property or we can apply
> > > the sparse property map technique?
> > 
> > setProperty handles filtering out default values, so this is already covered!
> 
> I think that you can then do:
> 
> +        if (auto characterRange = range.characterRange(); range &&
> characterRange)
> +            setProperty(AXPropertyName::TextInputMarkedTextMarkerRange, {
> range.start().objectID(), *characterRange });
> +    }
> 
> and save the copy to a temp.

C++ can't infer that type directly, but I can use the constructor inline and achieve the same result. I will upload with that revision included.
Comment 9 Joshua Hoffman 2023-11-28 11:13:47 PST
Created attachment 468787 [details]
Patch
Comment 10 EWS 2023-11-28 17:58:30 PST
Committed 271251@main (5965a5a92e80): <https://commits.webkit.org/271251@main>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 468787 [details].