Bug 146177 - AX: AXObjectCache should try to use an unignored accessibilityObject when posting a selection notification when on the border between two accessibilityObjects
Summary: AX: AXObjectCache should try to use an unignored accessibilityObject when pos...
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Accessibility (show other bugs)
Version: 528+ (Nightly build)
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Nobody
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2015-06-19 18:54 PDT by Doug Russell
Modified: 2015-06-24 13:52 PDT (History)
4 users (show)

See Also:


Attachments
patch (30.03 KB, patch)
2015-06-19 21:11 PDT, Doug Russell
no flags Details | Formatted Diff | Diff
patch (31.13 KB, patch)
2015-06-23 15:52 PDT, Doug Russell
no flags Details | Formatted Diff | Diff
patch (31.27 KB, patch)
2015-06-23 17:18 PDT, Doug Russell
darin: review+
Details | Formatted Diff | Diff
patch (31.49 KB, patch)
2015-06-24 12:10 PDT, Doug Russell
no flags Details | Formatted Diff | Diff
patch (31.44 KB, patch)
2015-06-24 12:19 PDT, Doug Russell
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Doug Russell 2015-06-19 18:54:10 PDT
When

1. AXObjectCache is asked to post a notification that selection state has changed
2. the selection is a caret
3. the selection is on a boundary between two accessibilityObjects and one of them is ignored

it is possible for AXObjectCache to try to resolve an observableObject() and end up with NULL because that element is ignored and won't have a useful tree.

This can be observed when tabbing between two links with a space in between them and observing that the accessibilityObject is the static text in between the links, not the second link.

The concrete fallout of this bug is difficulty correctly echoing links when tabbing between them.

This should be resolved by checking if the object is on a boundary and if the object on the other side of the boundary isn't ignored and using that instead.
Comment 1 Radar WebKit Bug Importer 2015-06-19 18:54:22 PDT
<rdar://problem/21472191>
Comment 2 Doug Russell 2015-06-19 21:11:32 PDT
Created attachment 255275 [details]
patch
Comment 3 Darin Adler 2015-06-23 13:59:26 PDT
Comment on attachment 255275 [details]
patch

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

Same comments apply to all the copied and pasted versions in DumpRenderTree; I commented on the WebKitTestRunner code.

> Source/WebCore/accessibility/AXObjectCache.cpp:1020
> +    Node* node = position.deprecatedNode();

Why is it right to use deprecatedNode here? Ryosuke, is that right?

> Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityNotificationHandler.mm:112
> +    static Class cls = nil;
> +    static dispatch_once_t onceToken;
> +    dispatch_once(&onceToken, ^{
> +        cls = NSClassFromString(@"WebAccessibilityObjectWrapper");
> +    });
> +    ASSERT(cls);
> +    return cls;

In WebKit code itself we often turn off the thread safety for globals. But if this is only used from one thread or if the thread safety for globals is turned on in WebKitTestRunner (which I think it could be), then this should just be:

    static Class wrapperClass = objc_getClass("WebAccessibilityObjectWrapper");
    return wrapperClass;

I also suggest using objc_getClass, since NSClassFromString just turns around and calls that.

> Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityNotificationHandler.mm:129
> +static JSValueRef MakeValueRefForValue(JSContextRef context, id value)

WebKit project does not capitalize function names.

> Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityNotificationHandler.mm:132
> +        return JSValueMakeString(context, JSRetainPtr<JSStringRef>(Adopt, [value createJSStringRef]).get());

We really need an adopt function to help write this kind of thing. The constructor syntax is super awkward.

> Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityNotificationHandler.mm:139
> +        return toJS(context, WTF::getPtr(WTR::AccessibilityUIElement::create(static_cast<PlatformUIElement>(value))));

No need to use WTF::getPtr here. Should just use the .get() function. The getPtr functions exists for generic programming that needs to work without knowing the type.

> Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityNotificationHandler.mm:147
> +static JSValueRef MakeArrayRefForArray(JSContextRef context, NSArray *array)

WebKit project does not capitalize function names.

> Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityNotificationHandler.mm:150
> +    JSValueRef arguments[count];

We normally don’t use the variable-sized array extension, but it’s probably oK for code only used in the test tool.

> Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityNotificationHandler.mm:158
> +static JSValueRef MakeObjectRefForDictionary(JSContextRef context, NSDictionary *dictionary)

WebKit project does not capitalize function names.

> Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityNotificationHandler.mm:164
> +
> +        JSRetainPtr<JSStringRef> propertyName = JSRetainPtr<JSStringRef>(Adopt, [key createJSStringRef]);

This should just be:

    JSRetainPtr<JSStringRef> propertyName { Adopt, [key createJSStringRef] };

But it would be even better if we had an adopt function so we could write:

    auto propertyName = adopt([key createJSStringRef]);
Comment 4 Doug Russell 2015-06-23 14:39:48 PDT
Can adopt just be a free function in JSRetainPtr.h like

template<typename T> JSRetainPtr<T> adopt(T value)
{
    return JSRetainPtr<T>(Adopt, value);
}

or should it be a static method on JSRetainPtr (or something else)?
Comment 5 Doug Russell 2015-06-23 15:52:05 PDT
Created attachment 255444 [details]
patch
Comment 6 Darin Adler 2015-06-23 17:01:06 PDT
(In reply to comment #4)
> Can adopt just be a free function in JSRetainPtr.h like
> 
> template<typename T> JSRetainPtr<T> adopt(T value)
> {
>     return JSRetainPtr<T>(Adopt, value);
> }
> 
> or should it be a static method on JSRetainPtr (or something else)?

It can and should be something like that, but that template is too generic and would apply to too many different classes. Instead we should just do overloading, I think:

    inline JSRetainPtr<JSStringRef> adopt(JSStringRef string)
    {
        return JSRetainPtr<JSStringRef>(Adopt, string);
    }

We can overload for other types as needed. Not sure what types JSRetainPtr supports.

Later we will want to make the JSRetainPtr constructor for Adopt be private and move to this function exclusively.
Comment 7 Doug Russell 2015-06-23 17:18:16 PDT
Created attachment 255452 [details]
patch
Comment 8 Darin Adler 2015-06-24 11:37:18 PDT
Comment on attachment 255452 [details]
patch

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

> Source/WebCore/accessibility/AXObjectCache.cpp:1010
> +    AccessibilityObject* object = getOrCreate(node);
> +    postTextStateChangeNotification(object, intent, selection);

Would read better without the local variable.

> Source/WebCore/accessibility/AXObjectCache.cpp:1028
> +    if (object && object->accessibilityIsIgnored()) {

Does getOrCreate actually ever return null? The name makes it sound like it wouldn’t.

> Source/WebCore/accessibility/AXObjectCache.cpp:1030
> +            if (AccessibilityObject *nextSibling = object->nextSiblingUnignored(1))

Incorrect formatting for WebKit coding style; * is in the wrong place. Also, I suggest using auto or auto* for this.

> Source/WebCore/accessibility/AXObjectCache.cpp:1033
> +            if (AccessibilityObject *previousSibling = object->previousSiblingUnignored(1))

Ditto.

> Source/WebCore/accessibility/AccessibilityObject.cpp:464
> +    if (limit < 0)
> +        limit = std::numeric_limits<int>::max();

This seems unnecessarily complex. If the default should be std::numeric_limits<int>::max(), then lets specify that default in the header, instead of making negative numbers magic. Also, we never use this feature, always passing 1 for limit, so this is dead untested code.

> Source/WebCore/accessibility/AccessibilityObject.cpp:465
> +    for (previous = this->previousSibling(); previous && previous->accessibilityIsIgnored(); previous = previous->previousSibling()) {

Normally we’d write this without this->

> Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperBase.mm:447
> +        if ((userInfo = dictionaryRemovingNonSupportedTypes(userInfo)))
> +            info = [NSDictionary dictionaryWithObjectsAndKeys:notificationName, @"notificationName", userInfo, @"userInfo", nil];
> +        else
>              info = [NSDictionary dictionaryWithObjectsAndKeys:notificationName, @"notificationName", nil];

Looks OK, but there is one sleazy shortcut we could take. No need to do the null check on userInfo because if it’s nil it will do the right thing, terminating the list early.

> Tools/DumpRenderTree/mac/AccessibilityNotificationHandler.mm:100
> +    static Class cls = nil;
> +    if (!cls)
> +        cls = NSClassFromString(@"WebAccessibilityObjectWrapper");

This is C++; no need for the if statement. This works:

    static Class cls = objc_getClass("WebAccessibilityObjectWrapper");

In fact, I would have just added "static" to the existing code instead of adding a new function. And even that change is not really needed; just makes this patch bigger for no significant benefit.

> Tools/DumpRenderTree/mac/AccessibilityNotificationHandler.mm:129
> +        return AccessibilityUIElement::makeJSAccessibilityUIElement(context, AccessibilityUIElement(value));

Is the explicit conversation to AccessibilityUIElement needed? Does it compile if you just leave it Oout?

> Tools/DumpRenderTree/mac/AccessibilityNotificationHandler.mm:152
> +    for (NSString *key in dictionary) {

Probably more efficient to use the method that enumerates with a block, since it gives both key and value and eliminates all the hash table lookups.

> Tools/DumpRenderTree/mac/AccessibilityNotificationHandler.mm:154
> +        auto propertyName = adopt([key createJSStringRef]);

Not great to do this outside the if since it’s only used inside the if.
Comment 9 Doug Russell 2015-06-24 12:10:58 PDT
Created attachment 255501 [details]
patch
Comment 10 Doug Russell 2015-06-24 12:19:54 PDT
Created attachment 255502 [details]
patch
Comment 11 WebKit Commit Bot 2015-06-24 13:52:03 PDT
Comment on attachment 255502 [details]
patch

Clearing flags on attachment: 255502

Committed r185924: <http://trac.webkit.org/changeset/185924>
Comment 12 WebKit Commit Bot 2015-06-24 13:52:07 PDT
All reviewed patches have been landed.  Closing bug.