Replace WTF::move with WTFMove
[WebKit-https.git] / Source / WebCore / accessibility / AXObjectCache.cpp
index f374897..538b955 100644 (file)
@@ -81,7 +81,7 @@
 #include "RenderTableRow.h"
 #include "RenderView.h"
 #include "ScrollView.h"
-#include <wtf/PassRefPtr.h>
+#include <wtf/DataLog.h>
 
 #if ENABLE(VIDEO)
 #include "MediaControlElements.h"
@@ -142,7 +142,9 @@ AXObjectCache::AXObjectCache(Document& document)
     , m_notificationPostTimer(*this, &AXObjectCache::notificationPostTimerFired)
     , m_passwordNotificationPostTimer(*this, &AXObjectCache::passwordNotificationPostTimerFired)
     , m_liveRegionChangedPostTimer(*this, &AXObjectCache::liveRegionChangedNotificationPostTimerFired)
+    , m_currentAriaModalNode(nullptr)
 {
+    findAriaModalNodes();
 }
 
 AXObjectCache::~AXObjectCache()
@@ -157,6 +159,91 @@ AXObjectCache::~AXObjectCache()
     }
 }
 
+void AXObjectCache::findAriaModalNodes()
+{
+    // Traverse the DOM tree to look for the aria-modal=true nodes.
+    for (Element* element = ElementTraversal::firstWithin(document().rootNode()); element; element = ElementTraversal::nextIncludingPseudo(*element)) {
+        
+        // Must have dialog or alertdialog role
+        if (!nodeHasRole(element, "dialog") && !nodeHasRole(element, "alertdialog"))
+            continue;
+        if (!equalIgnoringCase(element->fastGetAttribute(aria_modalAttr), "true"))
+            continue;
+        
+        m_ariaModalNodesSet.add(element);
+    }
+    
+    // Set the current valid aria-modal node if possible.
+    updateCurrentAriaModalNode();
+}
+
+void AXObjectCache::updateCurrentAriaModalNode()
+{
+    // There might be multiple nodes with aria-modal=true set.
+    // We use this function to pick the one we want.
+    m_currentAriaModalNode = nullptr;
+    if (m_ariaModalNodesSet.isEmpty())
+        return;
+    
+    // We only care about the nodes which are visible.
+    ListHashSet<RefPtr<Node>> visibleNodes;
+    for (auto& object : m_ariaModalNodesSet) {
+        if (isNodeVisible(object))
+            visibleNodes.add(object);
+    }
+    
+    if (visibleNodes.isEmpty())
+        return;
+    
+    // If any of the node are keyboard focused, we want to pick that.
+    Node* focusedNode = document().focusedElement();
+    for (auto& object : visibleNodes) {
+        if (focusedNode != nullptr && focusedNode->isDescendantOf(object.get())) {
+            m_currentAriaModalNode = object.get();
+            break;
+        }
+    }
+    
+    // If none of the nodes are focused, we want to pick the last dialog in the DOM.
+    if (!m_currentAriaModalNode)
+        m_currentAriaModalNode = visibleNodes.last().get();
+}
+
+bool AXObjectCache::isNodeVisible(Node* node) const
+{
+    if (!is<Element>(node))
+        return false;
+    
+    RenderObject* renderer = node->renderer();
+    if (!renderer)
+        return false;
+    const RenderStyle& style = renderer->style();
+    if (style.display() == NONE || style.visibility() != VISIBLE)
+        return false;
+    
+    // We also need to consider aria hidden status.
+    if (!isNodeAriaVisible(node))
+        return false;
+    
+    return true;
+}
+
+Node* AXObjectCache::ariaModalNode()
+{
+    // This function returns the valid aria modal node.
+    if (m_ariaModalNodesSet.isEmpty())
+        return nullptr;
+    
+    // Check the current valid aria modal node first.
+    // Usually when one dialog sets aria-modal=true, that dialog is the one we want.
+    if (isNodeVisible(m_currentAriaModalNode))
+        return m_currentAriaModalNode;
+    
+    // Recompute the valid aria modal node when m_currentAriaModalNode is null or hidden.
+    updateCurrentAriaModalNode();
+    return isNodeVisible(m_currentAriaModalNode) ? m_currentAriaModalNode : nullptr;
+}
+
 AccessibilityObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* areaElement)
 {
     // Find the corresponding accessibility object for the HTMLAreaElement. This should be
@@ -571,6 +658,12 @@ void AXObjectCache::remove(Node* node)
     remove(axID);
     m_nodeObjectMapping.remove(node);
 
+    // Cleanup for aria modal nodes.
+    if (m_currentAriaModalNode == node)
+        m_currentAriaModalNode = nullptr;
+    if (m_ariaModalNodesSet.contains(node))
+        m_ariaModalNodesSet.remove(node);
+    
     if (node->renderer()) {
         remove(node->renderer());
         return;
@@ -728,7 +821,7 @@ void AXObjectCache::notificationPostTimerFired()
     
     // In tests, posting notifications has a tendency to immediately queue up other notifications, which can lead to unexpected behavior
     // when the notification list is cleared at the end. Instead copy this list at the start.
-    auto notifications = WTF::move(m_notificationsToPost);
+    auto notifications = WTFMove(m_notificationsToPost);
     
     for (const auto& note : notifications) {
         AccessibilityObject* obj = note.first.get();
@@ -771,7 +864,7 @@ void AXObjectCache::passwordNotificationPostTimerFired()
 
     // In tests, posting notifications has a tendency to immediately queue up other notifications, which can lead to unexpected behavior
     // when the notification list is cleared at the end. Instead copy this list at the start.
-    auto notifications = WTF::move(m_passwordNotificationsToPost);
+    auto notifications = WTFMove(m_passwordNotificationsToPost);
 
     for (auto& notification : notifications)
         postTextStateChangePlatformNotification(notification.get(), AXTextEditTypeInsert, " ", VisiblePosition());
@@ -1248,10 +1341,34 @@ void AXObjectCache::handleAttributeChanged(const QualifiedName& attrName, Elemen
         childrenChanged(element->parentNode(), element);
     else if (attrName == aria_invalidAttr)
         postNotification(element, AXObjectCache::AXInvalidStatusChanged);
+    else if (attrName == aria_modalAttr)
+        handleAriaModalChange(element);
     else
         postNotification(element, AXObjectCache::AXAriaAttributeChanged);
 }
 
+void AXObjectCache::handleAriaModalChange(Node* node)
+{
+    if (!is<Element>(node))
+        return;
+    
+    if (!nodeHasRole(node, "dialog") && !nodeHasRole(node, "alertdialog"))
+        return;
+    
+    stopCachingComputedObjectAttributes();
+    if (equalIgnoringCase(downcast<Element>(*node).fastGetAttribute(aria_modalAttr), "true")) {
+        // Add the newly modified node to the modal nodes set, and set it to be the current valid aria modal node.
+        // We will recompute the current valid aria modal node in ariaModalNode() when this node is not visible.
+        m_ariaModalNodesSet.add(node);
+        m_currentAriaModalNode = node;
+    } else {
+        // Remove the node from the modal nodes set. There might be other visible modal nodes, so we recompute here.
+        m_ariaModalNodesSet.remove(node);
+        updateCurrentAriaModalNode();
+    }
+    startCachingComputedObjectAttributesUntilTreeMutates();
+}
+
 void AXObjectCache::labelChanged(Element* element)
 {
     ASSERT(is<HTMLLabelElement>(*element));
@@ -1316,11 +1433,8 @@ void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerD
     if (!domNode)
         return;
     
-    if (domNode->isHTMLElement()) {
-        HTMLInputElement* inputElement = domNode->toInputElement();
-        if (inputElement && inputElement->isPasswordField())
-            return;
-    }
+    if (is<HTMLInputElement>(*domNode) && downcast<HTMLInputElement>(*domNode).isPasswordField())
+        return;
     
     // find or create an accessibility object for this node
     AXObjectCache* cache = domNode->document().axObjectCache();