AX: [ATK] Events missing and state incorrect for aria-activedescendant
authorjdiggs@igalia.com <jdiggs@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 20 Oct 2017 16:30:20 +0000 (16:30 +0000)
committerjdiggs@igalia.com <jdiggs@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 20 Oct 2017 16:30:20 +0000 (16:30 +0000)
https://bugs.webkit.org/show_bug.cgi?id=178523

Reviewed by Chris Fleizach.

Source/WebCore:

When the aria-activedescendant of an element changes, emit object:state-changed:focused.
When a focused element has a valid active descendant, do not expose the focused state on
the element, but rather on the active descendant. Also expose the focusable state on the
active descendant.

Tests: accessibility/gtk/aria-activedescendant-changed-notification.html
       accessibility/gtk/aria-activedescendant.html

* accessibility/AccessibilityObject.cpp:
(WebCore::AccessibilityObject::isActiveDescendantOfFocusedContainer const):
(WebCore::AccessibilityObject::ariaActiveDescendantReferencingElements const):
* accessibility/AccessibilityObject.h:
* accessibility/AccessibilityRenderObject.cpp:
(WebCore::AccessibilityRenderObject::shouldNotifyActiveDescendant const):
* accessibility/atk/AXObjectCacheAtk.cpp:
(WebCore::AXObjectCache::postPlatformNotification):
* accessibility/atk/WebKitAccessibleWrapperAtk.cpp:
(setAtkStateSetFromCoreObject):

LayoutTests:

* accessibility/gtk/aria-activedescendant-changed-notification-expected.txt: Added.
* accessibility/gtk/aria-activedescendant-changed-notification.html: Added.
* accessibility/gtk/aria-activedescendant-expected.txt: Added.
* accessibility/gtk/aria-activedescendant.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@223766 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/accessibility/gtk/aria-activedescendant-changed-notification-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/gtk/aria-activedescendant-changed-notification.html [new file with mode: 0644]
LayoutTests/accessibility/gtk/aria-activedescendant-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/gtk/aria-activedescendant.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/accessibility/AccessibilityObject.cpp
Source/WebCore/accessibility/AccessibilityObject.h
Source/WebCore/accessibility/AccessibilityRenderObject.cpp
Source/WebCore/accessibility/atk/AXObjectCacheAtk.cpp
Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.cpp

index 873b46a..df04652 100644 (file)
@@ -1,3 +1,15 @@
+2017-10-20  Joanmarie Diggs  <jdiggs@igalia.com>
+
+        AX: [ATK] Events missing and state incorrect for aria-activedescendant
+        https://bugs.webkit.org/show_bug.cgi?id=178523
+
+        Reviewed by Chris Fleizach.
+
+        * accessibility/gtk/aria-activedescendant-changed-notification-expected.txt: Added.
+        * accessibility/gtk/aria-activedescendant-changed-notification.html: Added.
+        * accessibility/gtk/aria-activedescendant-expected.txt: Added.
+        * accessibility/gtk/aria-activedescendant.html: Added.
+
 2017-10-20  Per Arne Vollan  <pvollan@apple.com>
 
         [Win] Mark http/tests/navigation/keyboard-events-during-provisional-navigation.html and
diff --git a/LayoutTests/accessibility/gtk/aria-activedescendant-changed-notification-expected.txt b/LayoutTests/accessibility/gtk/aria-activedescendant-changed-notification-expected.txt
new file mode 100644 (file)
index 0000000..9b8a1ac
--- /dev/null
@@ -0,0 +1,15 @@
+This tests that changing the aria-activedescendant value results in a state-changed notification.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+AXFocusedUIElementChanged: AXRole: AXEmbedded
+AXFocusedUIElementChanged: AXRole: AXTextField
+AXFocusedUIElementChanged: AXRole: AXGroup
+AXFocusedUIElementChanged: AXRole: AXGroup
+AXFocusedUIElementChanged: AXRole: AXGroup
+AXFocusedUIElementChanged: AXRole: AXCheckBox
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/gtk/aria-activedescendant-changed-notification.html b/LayoutTests/accessibility/gtk/aria-activedescendant-changed-notification.html
new file mode 100644 (file)
index 0000000..878fa7b
--- /dev/null
@@ -0,0 +1,46 @@
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<div id="content">
+  <div id="test1" tabindex="0" role="application">
+    <div id="child1" role="group">test</div>
+  </div>
+  <div id="test2" tabindex="0" role="searchbox">
+    <div id="child2" role="group">test</div>
+  </div>
+  <div id="test3" tabindex="0" role="group">
+    <div id="child3" role="checkbox">test</div>
+  </div>
+</div>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+window.jsTestIsAsync = true;
+description("This tests that changing the aria-activedescendant value results in a state-changed notification.");
+
+if (window.testRunner && window.accessibilityController) {
+    accessibilityController.addNotificationListener(function(element, notification) {
+        if (notification != "AXFocusedUIElementChanged")
+            return;
+        debug(notification + ": " + element.role);
+    });
+
+    for (var i = 1; i <= 3; i++) {
+        var container = document.getElementById("test" + i);
+        container.focus();
+        container.setAttribute("aria-activedescendant", "child" + i);
+    }
+
+    document.getElementById("content").style.visibility = "hidden";
+
+    window.setTimeout(function() {
+        accessibilityController.removeNotificationListener();
+        finishJSTest();
+    }, 0);
+}
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/accessibility/gtk/aria-activedescendant-expected.txt b/LayoutTests/accessibility/gtk/aria-activedescendant-expected.txt
new file mode 100644 (file)
index 0000000..57645a8
--- /dev/null
@@ -0,0 +1,33 @@
+This tests the exposure of aria-activedescendant
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+
+test1 descendant: ''
+parent AXRole: AXEmbedded, isFocusable: true, isFocused: true
+child1 AXRole: AXGroup, isFocusable: false, isFocused: false
+
+test2 descendant: 'child2'
+parent AXRole: AXEmbedded, isFocusable: true, isFocused: false
+child2 AXRole: AXGroup, isFocusable: true, isFocused: true
+
+test3 descendant: ''
+parent AXRole: AXTextField, isFocusable: true, isFocused: true
+child3 AXRole: AXGroup, isFocusable: false, isFocused: false
+
+test4 descendant: 'child4'
+parent AXRole: AXTextField, isFocusable: true, isFocused: false
+child4 AXRole: AXGroup, isFocusable: true, isFocused: true
+
+test5 descendant: ''
+parent AXRole: AXGroup, isFocusable: true, isFocused: true
+child5 AXRole: AXCheckBox, isFocusable: false, isFocused: false
+
+test6 descendant: 'child6'
+parent AXRole: AXCheckBox, isFocusable: true, isFocused: true
+child6 AXRole: AXCheckBox, isFocusable: true, isFocused: true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/gtk/aria-activedescendant.html b/LayoutTests/accessibility/gtk/aria-activedescendant.html
new file mode 100644 (file)
index 0000000..8ff6dc4
--- /dev/null
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body id="body">
+<div id="content">
+  <div id="test1" tabindex="0" aria-activedescendant="" role="application">
+    <div id="child1" role="group">test</div>
+  </div>
+  <div id="test2" tabindex="0" aria-activedescendant="child2" role="application">
+    <div id="child2" role="group">test</div>
+  </div>
+  <div id="test3" tabindex="0" aria-activedescendant="" role="searchbox">
+    <div id="child3" role="group">test</div>
+  </div>
+  <div id="test4" tabindex="0" aria-activedescendant="child4" role="searchbox">
+    <div id="child4" role="group">test</div>
+  </div>
+  <div id="test5" tabindex="0" aria-activedescendant="" role="group">
+    <div id="child5" role="checkbox">test</div>
+  </div>
+  <div id="test6" tabindex="0" aria-activedescendant="child6" role="group">
+    <div id="child6" role="checkbox">test</div>
+  </div>
+</div>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+    description("This tests the exposure of aria-activedescendant");
+    if (window.accessibilityController) {
+        for (var i = 1; i <= 6; i++) {
+            var container = document.getElementById("test" + i);
+            debug("\ntest" + i + " descendant: '" + container.getAttribute("aria-activedescendant") + "'");
+
+            container.focus();
+            var axContainer = accessibilityController.focusedElement;
+            debug("parent " + axContainer.role + ", " +
+                 "isFocusable: " + axContainer.isFocusable + ", " +
+                  "isFocused: " + axContainer.isFocused);
+
+            var axChild = accessibilityController.accessibleElementById("child" + i);
+            debug("child" + i + " " + axChild.role + ", " +
+                  "isFocusable: " + axChild.isFocusable + ", " +
+                  "isFocused: " + axChild.isFocused);
+        }
+
+        document.getElementById("content").style.visibility = "hidden";
+    }
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
+
index 3199503..6cd371e 100644 (file)
@@ -1,3 +1,29 @@
+2017-10-20  Joanmarie Diggs  <jdiggs@igalia.com>
+
+        AX: [ATK] Events missing and state incorrect for aria-activedescendant
+        https://bugs.webkit.org/show_bug.cgi?id=178523
+
+        Reviewed by Chris Fleizach.
+
+        When the aria-activedescendant of an element changes, emit object:state-changed:focused.
+        When a focused element has a valid active descendant, do not expose the focused state on
+        the element, but rather on the active descendant. Also expose the focusable state on the
+        active descendant.
+
+        Tests: accessibility/gtk/aria-activedescendant-changed-notification.html
+               accessibility/gtk/aria-activedescendant.html
+
+        * accessibility/AccessibilityObject.cpp:
+        (WebCore::AccessibilityObject::isActiveDescendantOfFocusedContainer const):
+        (WebCore::AccessibilityObject::ariaActiveDescendantReferencingElements const):
+        * accessibility/AccessibilityObject.h:
+        * accessibility/AccessibilityRenderObject.cpp:
+        (WebCore::AccessibilityRenderObject::shouldNotifyActiveDescendant const):
+        * accessibility/atk/AXObjectCacheAtk.cpp:
+        (WebCore::AXObjectCache::postPlatformNotification):
+        * accessibility/atk/WebKitAccessibleWrapperAtk.cpp:
+        (setAtkStateSetFromCoreObject):
+
 2017-10-20  Ms2ger  <Ms2ger@igalia.com>
 
         Add the MAX_CLIENT_WAIT_TIMEOUT_WEBGL constant to WebGL2RenderingContext.
index e5adef8..a673e0d 100644 (file)
@@ -3407,6 +3407,23 @@ void AccessibilityObject::ariaElementsReferencedByAttribute(AccessibilityChildre
     }
 }
 
+bool AccessibilityObject::isActiveDescendantOfFocusedContainer() const
+{
+    AccessibilityChildrenVector containers;
+    ariaActiveDescendantReferencingElements(containers);
+    for (auto& container : containers) {
+        if (container->isFocused())
+            return true;
+    }
+
+    return false;
+}
+
+void AccessibilityObject::ariaActiveDescendantReferencingElements(AccessibilityChildrenVector& containers) const
+{
+    ariaElementsReferencedByAttribute(containers, aria_activedescendantAttr);
+}
+
 void AccessibilityObject::ariaControlsElements(AccessibilityChildrenVector& ariaControls) const
 {
     ariaElementsFromAttribute(ariaControls, aria_controlsAttr);
index 9d7299f..741f7c8 100644 (file)
@@ -670,6 +670,8 @@ public:
     static bool isARIAInput(AccessibilityRole);
 
     virtual bool supportsARIAOwns() const { return false; }
+    bool isActiveDescendantOfFocusedContainer() const;
+    void ariaActiveDescendantReferencingElements(AccessibilityChildrenVector&) const;
     void ariaControlsElements(AccessibilityChildrenVector&) const;
     void ariaControlsReferencingElements(AccessibilityChildrenVector&) const;
     void ariaDescribedByElements(AccessibilityChildrenVector&) const;
index fbca49e..e98359f 100644 (file)
@@ -2395,6 +2395,11 @@ AccessibilityObject* AccessibilityRenderObject::accessibilityHitTest(const IntPo
 
 bool AccessibilityRenderObject::shouldNotifyActiveDescendant() const
 {
+#if PLATFORM(GTK)
+    // According to the Core AAM spec, ATK expects object:state-changed:focused notifications
+    // whenever the active descendant changes.
+    return true;
+#endif
     // We want to notify that the combo box has changed its active descendant,
     // but we do not want to change the focus, because focus should remain with the combo box.
     if (isComboBox())
index 96eb89e..9659d63 100644 (file)
@@ -280,6 +280,11 @@ void AXObjectCache::postPlatformNotification(AccessibilityObject* coreObject, AX
         atk_object_notify_state_change(axObject, ATK_STATE_REQUIRED, coreObject->isRequired());
         break;
 
+    case AXActiveDescendantChanged:
+        if (AccessibilityObject* descendant = coreObject->activeDescendant())
+            platformHandleFocusedUIElementChanged(nullptr, descendant->node());
+        break;
+
     default:
         break;
     }
index 7f2f4fb..99f8538 100644 (file)
@@ -962,8 +962,15 @@ static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkSta
     if (coreObject->canSetFocusAttribute())
         atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
 
-    if (coreObject->isFocused() || isTextWithCaret(coreObject))
+    // According to the Core AAM, if the element which is focused has a valid aria-activedescendant,
+    // we should not expose the focused state on the element which is actually focused, but instead
+    // on its active descendant.
+    if ((coreObject->isFocused() && !coreObject->activeDescendant()) || isTextWithCaret(coreObject))
         atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
+    else if (coreObject->isActiveDescendantOfFocusedContainer()) {
+        atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
+        atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
+    }
 
     if (coreObject->orientation() == AccessibilityOrientationHorizontal)
         atk_state_set_add_state(stateSet, ATK_STATE_HORIZONTAL);