Changeset 191931 in webkit


Ignore:
Timestamp:
Nov 2, 2015 5:52:38 PM (8 years ago)
Author:
n_wang@apple.com
Message:

AX: Add support for ARIA 1.1 attribute 'aria-modal' for dialog and alertdialog
https://bugs.webkit.org/show_bug.cgi?id=138566

Reviewed by Chris Fleizach.

Source/WebCore:

Added support for aria-modal attribute on dialog/alertdialog roles.
When modal dialog is displayed, all other contents will be unaccessible.

Tests: accessibility/aria-modal-multiple-dialogs.html

accessibility/aria-modal.html

  • accessibility/AXObjectCache.cpp:

(WebCore::AXObjectCache::AXObjectCache):
(WebCore::AXObjectCache::~AXObjectCache):
(WebCore::AXObjectCache::findAriaModalNodes):
(WebCore::AXObjectCache::updateCurrentAriaModalNode):
(WebCore::AXObjectCache::isNodeVisible):
(WebCore::AXObjectCache::ariaModalNode):
(WebCore::AXObjectCache::focusedImageMapUIElement):
(WebCore::AXObjectCache::remove):
(WebCore::AXObjectCache::handleAttributeChanged):
(WebCore::AXObjectCache::handleAriaModalChange):
(WebCore::AXObjectCache::labelChanged):

  • accessibility/AXObjectCache.h:

(WebCore::AXObjectCache::handleActiveDescendantChanged):
(WebCore::AXObjectCache::handleAriaExpandedChange):
(WebCore::AXObjectCache::handleAriaRoleChanged):
(WebCore::AXObjectCache::handleAriaModalChange):
(WebCore::AXObjectCache::handleFocusedUIElementChanged):
(WebCore::AXObjectCache::handleScrollbarUpdate):
(WebCore::AXObjectCache::handleAttributeChanged):

  • accessibility/AccessibilityObject.cpp:

(WebCore::AccessibilityObject::ariaCurrentState):
(WebCore::AccessibilityObject::isAriaModalDescendant):
(WebCore::AccessibilityObject::ignoredFromARIAModalPresence):
(WebCore::AccessibilityObject::hasTagName):
(WebCore::AccessibilityObject::defaultObjectInclusion):

  • accessibility/AccessibilityObject.h:
  • html/HTMLAttributeNames.in:

LayoutTests:

  • accessibility/aria-modal-expected.txt: Added.
  • accessibility/aria-modal-multiple-dialogs-expected.txt: Added.
  • accessibility/aria-modal-multiple-dialogs.html: Added.
  • accessibility/aria-modal.html: Added.
Location:
trunk
Files:
4 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r191928 r191931  
     12015-11-02  Nan Wang  <n_wang@apple.com>
     2
     3        AX: Add support for ARIA 1.1 attribute 'aria-modal' for dialog and alertdialog
     4        https://bugs.webkit.org/show_bug.cgi?id=138566
     5
     6        Reviewed by Chris Fleizach.
     7
     8        * accessibility/aria-modal-expected.txt: Added.
     9        * accessibility/aria-modal-multiple-dialogs-expected.txt: Added.
     10        * accessibility/aria-modal-multiple-dialogs.html: Added.
     11        * accessibility/aria-modal.html: Added.
     12
    1132015-11-02  Brady Eidson  <beidson@apple.com>
    214
  • trunk/Source/WebCore/ChangeLog

    r191928 r191931  
     12015-11-02  Nan Wang  <n_wang@apple.com>
     2
     3        AX: Add support for ARIA 1.1 attribute 'aria-modal' for dialog and alertdialog
     4        https://bugs.webkit.org/show_bug.cgi?id=138566
     5
     6        Reviewed by Chris Fleizach.
     7
     8        Added support for aria-modal attribute on dialog/alertdialog roles.
     9        When modal dialog is displayed, all other contents will be unaccessible.
     10
     11        Tests: accessibility/aria-modal-multiple-dialogs.html
     12               accessibility/aria-modal.html
     13
     14        * accessibility/AXObjectCache.cpp:
     15        (WebCore::AXObjectCache::AXObjectCache):
     16        (WebCore::AXObjectCache::~AXObjectCache):
     17        (WebCore::AXObjectCache::findAriaModalNodes):
     18        (WebCore::AXObjectCache::updateCurrentAriaModalNode):
     19        (WebCore::AXObjectCache::isNodeVisible):
     20        (WebCore::AXObjectCache::ariaModalNode):
     21        (WebCore::AXObjectCache::focusedImageMapUIElement):
     22        (WebCore::AXObjectCache::remove):
     23        (WebCore::AXObjectCache::handleAttributeChanged):
     24        (WebCore::AXObjectCache::handleAriaModalChange):
     25        (WebCore::AXObjectCache::labelChanged):
     26        * accessibility/AXObjectCache.h:
     27        (WebCore::AXObjectCache::handleActiveDescendantChanged):
     28        (WebCore::AXObjectCache::handleAriaExpandedChange):
     29        (WebCore::AXObjectCache::handleAriaRoleChanged):
     30        (WebCore::AXObjectCache::handleAriaModalChange):
     31        (WebCore::AXObjectCache::handleFocusedUIElementChanged):
     32        (WebCore::AXObjectCache::handleScrollbarUpdate):
     33        (WebCore::AXObjectCache::handleAttributeChanged):
     34        * accessibility/AccessibilityObject.cpp:
     35        (WebCore::AccessibilityObject::ariaCurrentState):
     36        (WebCore::AccessibilityObject::isAriaModalDescendant):
     37        (WebCore::AccessibilityObject::ignoredFromARIAModalPresence):
     38        (WebCore::AccessibilityObject::hasTagName):
     39        (WebCore::AccessibilityObject::defaultObjectInclusion):
     40        * accessibility/AccessibilityObject.h:
     41        * html/HTMLAttributeNames.in:
     42
    1432015-11-02  Brady Eidson  <beidson@apple.com>
    244
  • trunk/Source/WebCore/accessibility/AXObjectCache.cpp

    r191327 r191931  
    143143    , m_passwordNotificationPostTimer(*this, &AXObjectCache::passwordNotificationPostTimerFired)
    144144    , m_liveRegionChangedPostTimer(*this, &AXObjectCache::liveRegionChangedNotificationPostTimerFired)
    145 {
     145    , m_currentAriaModalNode(nullptr)
     146{
     147    findAriaModalNodes();
    146148}
    147149
     
    156158        removeAXID(object.get());
    157159    }
     160}
     161
     162void AXObjectCache::findAriaModalNodes()
     163{
     164    // Traverse the DOM tree to look for the aria-modal=true nodes.
     165    for (Element* element = ElementTraversal::firstWithin(document().rootNode()); element; element = ElementTraversal::nextIncludingPseudo(*element)) {
     166       
     167        // Must have dialog or alertdialog role
     168        if (!nodeHasRole(element, "dialog") && !nodeHasRole(element, "alertdialog"))
     169            continue;
     170        if (!equalIgnoringCase(element->fastGetAttribute(aria_modalAttr), "true"))
     171            continue;
     172       
     173        m_ariaModalNodesSet.add(element);
     174    }
     175   
     176    // Set the current valid aria-modal node if possible.
     177    updateCurrentAriaModalNode();
     178}
     179
     180void AXObjectCache::updateCurrentAriaModalNode()
     181{
     182    // There might be multiple nodes with aria-modal=true set.
     183    // We use this function to pick the one we want.
     184    m_currentAriaModalNode = nullptr;
     185    if (m_ariaModalNodesSet.isEmpty())
     186        return;
     187   
     188    // We only care about the nodes which are visible.
     189    ListHashSet<RefPtr<Node>> visibleNodes;
     190    for (auto& object : m_ariaModalNodesSet) {
     191        if (isNodeVisible(object))
     192            visibleNodes.add(object);
     193    }
     194   
     195    if (visibleNodes.isEmpty())
     196        return;
     197   
     198    // If any of the node are keyboard focused, we want to pick that.
     199    Node* focusedNode = document().focusedElement();
     200    for (auto& object : visibleNodes) {
     201        if (focusedNode != nullptr && focusedNode->isDescendantOf(object.get())) {
     202            m_currentAriaModalNode = object.get();
     203            break;
     204        }
     205    }
     206   
     207    // If none of the nodes are focused, we want to pick the last dialog in the DOM.
     208    if (!m_currentAriaModalNode)
     209        m_currentAriaModalNode = visibleNodes.last().get();
     210}
     211
     212bool AXObjectCache::isNodeVisible(Node* node) const
     213{
     214    if (!is<Element>(node))
     215        return false;
     216   
     217    RenderObject* renderer = node->renderer();
     218    if (!renderer)
     219        return false;
     220    const RenderStyle& style = renderer->style();
     221    if (style.display() == NONE || style.visibility() != VISIBLE)
     222        return false;
     223   
     224    // We also need to consider aria hidden status.
     225    if (!isNodeAriaVisible(node))
     226        return false;
     227   
     228    return true;
     229}
     230
     231Node* AXObjectCache::ariaModalNode()
     232{
     233    // This function returns the valid aria modal node.
     234    if (m_ariaModalNodesSet.isEmpty())
     235        return nullptr;
     236   
     237    // Check the current valid aria modal node first.
     238    // Usually when one dialog sets aria-modal=true, that dialog is the one we want.
     239    if (isNodeVisible(m_currentAriaModalNode))
     240        return m_currentAriaModalNode;
     241   
     242    // Recompute the valid aria modal node when m_currentAriaModalNode is null or hidden.
     243    updateCurrentAriaModalNode();
     244    return isNodeVisible(m_currentAriaModalNode) ? m_currentAriaModalNode : nullptr;
    158245}
    159246
     
    572659    m_nodeObjectMapping.remove(node);
    573660
     661    // Cleanup for aria modal nodes.
     662    if (m_currentAriaModalNode == node)
     663        m_currentAriaModalNode = nullptr;
     664    if (m_ariaModalNodesSet.contains(node))
     665        m_ariaModalNodesSet.remove(node);
     666   
    574667    if (node->renderer()) {
    575668        remove(node->renderer());
     
    12491342    else if (attrName == aria_invalidAttr)
    12501343        postNotification(element, AXObjectCache::AXInvalidStatusChanged);
     1344    else if (attrName == aria_modalAttr)
     1345        handleAriaModalChange(element);
    12511346    else
    12521347        postNotification(element, AXObjectCache::AXAriaAttributeChanged);
     1348}
     1349
     1350void AXObjectCache::handleAriaModalChange(Node* node)
     1351{
     1352    if (!is<Element>(node))
     1353        return;
     1354   
     1355    if (!nodeHasRole(node, "dialog") && !nodeHasRole(node, "alertdialog"))
     1356        return;
     1357   
     1358    stopCachingComputedObjectAttributes();
     1359    if (equalIgnoringCase(downcast<Element>(*node).fastGetAttribute(aria_modalAttr), "true")) {
     1360        // Add the newly modified node to the modal nodes set, and set it to be the current valid aria modal node.
     1361        // We will recompute the current valid aria modal node in ariaModalNode() when this node is not visible.
     1362        m_ariaModalNodesSet.add(node);
     1363        m_currentAriaModalNode = node;
     1364    } else {
     1365        // Remove the node from the modal nodes set. There might be other visible modal nodes, so we recompute here.
     1366        m_ariaModalNodesSet.remove(node);
     1367        updateCurrentAriaModalNode();
     1368    }
     1369    startCachingComputedObjectAttributesUntilTreeMutates();
    12531370}
    12541371
  • trunk/Source/WebCore/accessibility/AXObjectCache.h

    r187278 r191931  
    129129    void handleAriaExpandedChange(Node*);
    130130    void handleScrollbarUpdate(ScrollView*);
     131   
     132    void handleAriaModalChange(Node*);
     133    Node* ariaModalNode();
    131134
    132135    void handleAttributeChanged(const QualifiedName& attrName, Element*);
     
    272275    void handleLiveRegionCreated(Node*);
    273276    void handleMenuItemSelected(Node*);
     277   
     278    // aria-modal related
     279    void findAriaModalNodes();
     280    void updateCurrentAriaModalNode();
     281    bool isNodeVisible(Node*) const;
    274282
    275283    Document& m_document;
     
    294302    Timer m_liveRegionChangedPostTimer;
    295303    ListHashSet<RefPtr<AccessibilityObject>> m_liveRegionObjectsSet;
     304   
     305    Node* m_currentAriaModalNode;
     306    ListHashSet<Node*> m_ariaModalNodesSet;
    296307
    297308    AXTextStateChangeIntent m_textSelectionIntent;
     
    349360inline void AXObjectCache::handleAriaExpandedChange(Node*) { }
    350361inline void AXObjectCache::handleAriaRoleChanged(Node*) { }
     362inline void AXObjectCache::handleAriaModalChange(Node*) { }
    351363inline void AXObjectCache::handleFocusedUIElementChanged(Node*, Node*) { }
    352364inline void AXObjectCache::handleScrollbarUpdate(ScrollView*) { }
  • trunk/Source/WebCore/accessibility/AccessibilityObject.cpp

    r190833 r191931  
    3636#include "DOMTokenList.h"
    3737#include "Editor.h"
     38#include "ElementIterator.h"
    3839#include "EventHandler.h"
    3940#include "FloatRect.h"
     
    18451846    // Any value not included in the list of allowed values should be treated as "true".
    18461847    return ARIACurrentTrue;
     1848}
     1849
     1850bool AccessibilityObject::isAriaModalDescendant(Node* ariaModalNode) const
     1851{
     1852    if (!ariaModalNode || !this->element())
     1853        return false;
     1854   
     1855    if (this->element() == ariaModalNode)
     1856        return true;
     1857   
     1858    // ARIA 1.1 aria-modal, indicates whether an element is modal when displayed.
     1859    // For the decendants of the modal object, they should also be considered as aria-modal=true.
     1860    for (auto& ancestor : elementAncestors(this->element())) {
     1861        if (&ancestor == ariaModalNode)
     1862            return true;
     1863    }
     1864    return false;
     1865}
     1866
     1867bool AccessibilityObject::ignoredFromARIAModalPresence() const
     1868{
     1869    // We shouldn't ignore the top node.
     1870    if (!node() || !node()->parentNode())
     1871        return false;
     1872   
     1873    AXObjectCache* cache = axObjectCache();
     1874    if (!cache)
     1875        return false;
     1876   
     1877    // ariaModalNode is the current displayed modal dialog.
     1878    Node* ariaModalNode = cache->ariaModalNode();
     1879    if (!ariaModalNode)
     1880        return false;
     1881   
     1882    // We only want to ignore the objects within the same frame as the modal dialog.
     1883    if (ariaModalNode->document().frame() != this->frame())
     1884        return false;
     1885   
     1886    return !isAriaModalDescendant(ariaModalNode);
    18471887}
    18481888
     
    27872827        return IgnoreObject;
    27882828   
     2829    if (ignoredFromARIAModalPresence())
     2830        return IgnoreObject;
     2831   
    27892832    if (isPresentationalChildOfAriaRole())
    27902833        return IgnoreObject;
  • trunk/Source/WebCore/accessibility/AccessibilityObject.h

    r190833 r191931  
    632632    AccessibilityARIACurrentState ariaCurrentState() const;
    633633   
     634    // This function checks if the object should be ignored when there's a modal dialog displayed.
     635    bool ignoredFromARIAModalPresence() const;
     636    bool isAriaModalDescendant(Node*) const;
     637   
    634638    bool supportsARIASetSize() const;
    635639    bool supportsARIAPosInSet() const;
  • trunk/Source/WebCore/html/HTMLAttributeNames.in

    r190833 r191931  
    3838aria-level
    3939aria-live
     40aria-modal
    4041aria-multiline
    4142aria-multiselectable
Note: See TracChangeset for help on using the changeset viewer.