Replace WTF::move with WTFMove
[WebKit-https.git] / Source / WebCore / accessibility / AXObjectCache.cpp
index 925ebbb..538b955 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, 2009, 2010 Apple Inc. All rights reserved.
+ * Copyright (C) 2008, 2009, 2010, 2015 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -10,7 +10,7 @@
  * 2.  Redistributions in binary form must reproduce the above copyright
  *     notice, this list of conditions and the following disclaimer in the
  *     documentation and/or other materials provided with the distribution.
- * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
  *     its contributors may be used to endorse or promote products derived
  *     from this software without specific prior written permission.
  *
@@ -48,7 +48,6 @@
 #include "AccessibilitySVGRoot.h"
 #include "AccessibilityScrollView.h"
 #include "AccessibilityScrollbar.h"
-#include "AccessibilitySearchFieldButtons.h"
 #include "AccessibilitySlider.h"
 #include "AccessibilitySpinButton.h"
 #include "AccessibilityTable.h"
 #include "AccessibilityTableColumn.h"
 #include "AccessibilityTableHeaderContainer.h"
 #include "AccessibilityTableRow.h"
+#include "AccessibilityTree.h"
+#include "AccessibilityTreeItem.h"
 #include "Document.h"
 #include "Editor.h"
+#include "ElementIterator.h"
 #include "FocusController.h"
 #include "Frame.h"
 #include "HTMLAreaElement.h"
+#include "HTMLCanvasElement.h"
 #include "HTMLImageElement.h"
 #include "HTMLInputElement.h"
 #include "HTMLLabelElement.h"
 #include "RenderMenuList.h"
 #include "RenderMeter.h"
 #include "RenderProgress.h"
+#include "RenderSVGRoot.h"
 #include "RenderSlider.h"
 #include "RenderTable.h"
 #include "RenderTableCell.h"
 #include "RenderTableRow.h"
 #include "RenderView.h"
 #include "ScrollView.h"
-#include <wtf/PassRefPtr.h>
+#include <wtf/DataLog.h>
 
 #if ENABLE(VIDEO)
 #include "MediaControlElements.h"
@@ -87,6 +91,10 @@ namespace WebCore {
 
 using namespace HTMLNames;
 
+// Post value change notifications for password fields or elements contained in password fields at a 40hz interval to thwart analysis of typing cadence
+static double AccessibilityPasswordValueChangeNotificationInterval = 0.025;
+static double AccessibilityLiveRegionChangedNotificationInterval = 0.020;
+
 AccessibilityObjectInclusion AXComputedObjectAttributeCache::getIgnored(AXID id) const
 {
     HashMap<AXID, CachedAXObjectAttributes>::const_iterator it = m_idMapping.find(id);
@@ -108,15 +116,41 @@ void AXComputedObjectAttributeCache::setIgnored(AXID id, AccessibilityObjectIncl
 bool AXObjectCache::gAccessibilityEnabled = false;
 bool AXObjectCache::gAccessibilityEnhancedUserInterfaceEnabled = false;
 
+void AXObjectCache::enableAccessibility()
+{
+    gAccessibilityEnabled = true;
+}
+
+void AXObjectCache::disableAccessibility()
+{
+    gAccessibilityEnabled = false;
+}
+
+void AXObjectCache::setEnhancedUserInterfaceAccessibility(bool flag)
+{
+    gAccessibilityEnhancedUserInterfaceEnabled = flag;
+#if PLATFORM(MAC)
+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100
+    if (flag)
+        enableAccessibility();
+#endif
+#endif
+}
+
 AXObjectCache::AXObjectCache(Document& document)
     : m_document(document)
-    , m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired)
+    , m_notificationPostTimer(*this, &AXObjectCache::notificationPostTimerFired)
+    , m_passwordNotificationPostTimer(*this, &AXObjectCache::passwordNotificationPostTimerFired)
+    , m_liveRegionChangedPostTimer(*this, &AXObjectCache::liveRegionChangedNotificationPostTimerFired)
+    , m_currentAriaModalNode(nullptr)
 {
+    findAriaModalNodes();
 }
 
 AXObjectCache::~AXObjectCache()
 {
     m_notificationPostTimer.stop();
+    m_liveRegionChangedPostTimer.stop();
 
     for (const auto& object : m_objects.values()) {
         detachWrapper(object.get(), CacheDestroyed);
@@ -125,46 +159,131 @@ 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
     // in the list of children for its corresponding image.
     if (!areaElement)
-        return 0;
+        return nullptr;
     
     HTMLImageElement* imageElement = areaElement->imageElement();
     if (!imageElement)
-        return 0;
+        return nullptr;
     
     AccessibilityObject* axRenderImage = areaElement->document().axObjectCache()->getOrCreate(imageElement);
     if (!axRenderImage)
-        return 0;
+        return nullptr;
     
     for (const auto& child : axRenderImage->children()) {
-        if (!child->isImageMapLink())
+        if (!is<AccessibilityImageMapLink>(*child))
             continue;
         
-        if (toAccessibilityImageMapLink(child.get())->areaElement() == areaElement)
+        if (downcast<AccessibilityImageMapLink>(*child).areaElement() == areaElement)
             return child.get();
     }    
     
-    return 0;
+    return nullptr;
 }
     
 AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page)
 {
     if (!gAccessibilityEnabled)
-        return 0;
+        return nullptr;
 
     // get the focused node in the page
     Document* focusedDocument = page->focusController().focusedOrMainFrame().document();
     Element* focusedElement = focusedDocument->focusedElement();
-    if (focusedElement && isHTMLAreaElement(focusedElement))
-        return focusedImageMapUIElement(toHTMLAreaElement(focusedElement));
+    if (is<HTMLAreaElement>(focusedElement))
+        return focusedImageMapUIElement(downcast<HTMLAreaElement>(focusedElement));
 
     AccessibilityObject* obj = focusedDocument->axObjectCache()->getOrCreate(focusedElement ? static_cast<Node*>(focusedElement) : focusedDocument);
     if (!obj)
-        return 0;
+        return nullptr;
 
     if (obj->shouldFocusActiveDescendant()) {
         if (AccessibilityObject* descendant = obj->activeDescendant())
@@ -181,12 +300,12 @@ AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page)
 AccessibilityObject* AXObjectCache::get(Widget* widget)
 {
     if (!widget)
-        return 0;
+        return nullptr;
         
     AXID axID = m_widgetObjectMapping.get(widget);
     ASSERT(!HashTraits<AXID>::isDeletedValue(axID));
     if (!axID)
-        return 0;
+        return nullptr;
     
     return m_objects.get(axID);    
 }
@@ -194,12 +313,12 @@ AccessibilityObject* AXObjectCache::get(Widget* widget)
 AccessibilityObject* AXObjectCache::get(RenderObject* renderer)
 {
     if (!renderer)
-        return 0;
+        return nullptr;
     
     AXID axID = m_renderObjectMapping.get(renderer);
     ASSERT(!HashTraits<AXID>::isDeletedValue(axID));
     if (!axID)
-        return 0;
+        return nullptr;
 
     return m_objects.get(axID);    
 }
@@ -207,7 +326,7 @@ AccessibilityObject* AXObjectCache::get(RenderObject* renderer)
 AccessibilityObject* AXObjectCache::get(Node* node)
 {
     if (!node)
-        return 0;
+        return nullptr;
 
     AXID renderID = node->renderer() ? m_renderObjectMapping.get(node->renderer()) : 0;
     ASSERT(!HashTraits<AXID>::isDeletedValue(renderID));
@@ -220,14 +339,14 @@ AccessibilityObject* AXObjectCache::get(Node* node)
         // rendered, but later something changes and it gets a renderer (like if it's
         // reparented).
         remove(nodeID);
-        return 0;
+        return nullptr;
     }
 
     if (renderID)
         return m_objects.get(renderID);
 
     if (!nodeID)
-        return 0;
+        return nullptr;
 
     return m_objects.get(nodeID);
 }
@@ -236,13 +355,19 @@ AccessibilityObject* AXObjectCache::get(Node* node)
 // FIXME: This should take a const char*, but one caller passes nullAtom.
 bool nodeHasRole(Node* node, const String& role)
 {
-    if (!node || !node->isElementNode())
+    if (!node || !is<Element>(node))
         return false;
 
-    return equalIgnoringCase(toElement(node)->getAttribute(roleAttr), role);
+    auto& roleValue = downcast<Element>(*node).fastGetAttribute(roleAttr);
+    if (role.isNull())
+        return roleValue.isEmpty();
+    if (roleValue.isEmpty())
+        return false;
+
+    return SpaceSplitString(roleValue, true).contains(role);
 }
 
-static PassRefPtr<AccessibilityObject> createFromRenderer(RenderObject* renderer)
+static Ref<AccessibilityObject> createFromRenderer(RenderObject* renderer)
 {
     // FIXME: How could renderer->node() ever not be an Element?
     Node* node = renderer->node();
@@ -254,62 +379,61 @@ static PassRefPtr<AccessibilityObject> createFromRenderer(RenderObject* renderer
         return AccessibilityList::create(renderer);
 
     // aria tables
-    if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid"))
+    if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid") || nodeHasRole(node, "table"))
         return AccessibilityARIAGrid::create(renderer);
     if (nodeHasRole(node, "row"))
         return AccessibilityARIAGridRow::create(renderer);
-    if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "columnheader") || nodeHasRole(node, "rowheader"))
+    if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "cell") || nodeHasRole(node, "columnheader") || nodeHasRole(node, "rowheader"))
         return AccessibilityARIAGridCell::create(renderer);
 
+    // aria tree
+    if (nodeHasRole(node, "tree"))
+        return AccessibilityTree::create(renderer);
+    if (nodeHasRole(node, "treeitem"))
+        return AccessibilityTreeItem::create(renderer);
+
 #if ENABLE(VIDEO)
     // media controls
     if (node && node->isMediaControlElement())
         return AccessibilityMediaControl::create(renderer);
 #endif
 
-#if ENABLE(SVG)
-    if (renderer->isSVGRoot())
+    if (is<RenderSVGRoot>(*renderer))
         return AccessibilitySVGRoot::create(renderer);
-#endif
-    
-    // Search field buttons
-    if (node && node->isElementNode() && toElement(node)->isSearchFieldCancelButtonElement())
-        return AccessibilitySearchFieldCancelButton::create(renderer);
     
-    if (renderer->isBoxModelObject()) {
-        RenderBoxModelObject* cssBox = toRenderBoxModelObject(renderer);
-        if (cssBox->isListBox())
-            return AccessibilityListBox::create(toRenderListBox(cssBox));
-        if (cssBox->isMenuList())
-            return AccessibilityMenuList::create(toRenderMenuList(cssBox));
+    if (is<RenderBoxModelObject>(*renderer)) {
+        RenderBoxModelObject& cssBox = downcast<RenderBoxModelObject>(*renderer);
+        if (is<RenderListBox>(cssBox))
+            return AccessibilityListBox::create(&downcast<RenderListBox>(cssBox));
+        if (is<RenderMenuList>(cssBox))
+            return AccessibilityMenuList::create(&downcast<RenderMenuList>(cssBox));
 
         // standard tables
-        if (cssBox->isTable())
-            return AccessibilityTable::create(toRenderTable(cssBox));
-        if (cssBox->isTableRow())
-            return AccessibilityTableRow::create(toRenderTableRow(cssBox));
-        if (cssBox->isTableCell())
-            return AccessibilityTableCell::create(toRenderTableCell(cssBox));
-
-#if ENABLE(PROGRESS_ELEMENT)
+        if (is<RenderTable>(cssBox))
+            return AccessibilityTable::create(&downcast<RenderTable>(cssBox));
+        if (is<RenderTableRow>(cssBox))
+            return AccessibilityTableRow::create(&downcast<RenderTableRow>(cssBox));
+        if (is<RenderTableCell>(cssBox))
+            return AccessibilityTableCell::create(&downcast<RenderTableCell>(cssBox));
+
         // progress bar
-        if (cssBox->isProgress())
-            return AccessibilityProgressIndicator::create(toRenderProgress(cssBox));
-#endif
+        if (is<RenderProgress>(cssBox))
+            return AccessibilityProgressIndicator::create(&downcast<RenderProgress>(cssBox));
+
 #if ENABLE(METER_ELEMENT)
-        if (cssBox->isMeter())
-            return AccessibilityProgressIndicator::create(toRenderMeter(cssBox));
+        if (is<RenderMeter>(cssBox))
+            return AccessibilityProgressIndicator::create(&downcast<RenderMeter>(cssBox));
 #endif
 
         // input type=range
-        if (cssBox->isSlider())
-            return AccessibilitySlider::create(toRenderSlider(cssBox));
+        if (is<RenderSlider>(cssBox))
+            return AccessibilitySlider::create(&downcast<RenderSlider>(cssBox));
     }
 
     return AccessibilityRenderObject::create(renderer);
 }
 
-static PassRefPtr<AccessibilityObject> createFromNode(Node* node)
+static Ref<AccessibilityObject> createFromNode(Node* node)
 {
     return AccessibilityNodeObject::create(node);
 }
@@ -317,16 +441,16 @@ static PassRefPtr<AccessibilityObject> createFromNode(Node* node)
 AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget)
 {
     if (!widget)
-        return 0;
+        return nullptr;
 
     if (AccessibilityObject* obj = get(widget))
         return obj;
     
-    RefPtr<AccessibilityObject> newObj = 0;
-    if (widget->isFrameView())
-        newObj = AccessibilityScrollView::create(toScrollView(widget));
-    else if (widget->isScrollbar())
-        newObj = AccessibilityScrollbar::create(toScrollbar(widget));
+    RefPtr<AccessibilityObject> newObj;
+    if (is<ScrollView>(*widget))
+        newObj = AccessibilityScrollView::create(downcast<ScrollView>(widget));
+    else if (is<Scrollbar>(*widget))
+        newObj = AccessibilityScrollbar::create(downcast<Scrollbar>(widget));
 
     // Will crash later if we have two objects for the same widget.
     ASSERT(!get(widget));
@@ -334,7 +458,7 @@ AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget)
     // Catch the case if an (unsupported) widget type is used. Only FrameView and ScrollBar are supported now.
     ASSERT(newObj);
     if (!newObj)
-        return 0;
+        return nullptr;
 
     getAXID(newObj.get());
     
@@ -348,7 +472,7 @@ AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget)
 AccessibilityObject* AXObjectCache::getOrCreate(Node* node)
 {
     if (!node)
-        return 0;
+        return nullptr;
 
     if (AccessibilityObject* obj = get(node))
         return obj;
@@ -357,20 +481,25 @@ AccessibilityObject* AXObjectCache::getOrCreate(Node* node)
         return getOrCreate(node->renderer());
 
     if (!node->parentElement())
-        return 0;
+        return nullptr;
     
     // It's only allowed to create an AccessibilityObject from a Node if it's in a canvas subtree.
     // Or if it's a hidden element, but we still want to expose it because of other ARIA attributes.
-    bool inCanvasSubtree = node->parentElement()->isInCanvasSubtree();
-    bool isHidden = !node->renderer() && isNodeAriaVisible(node);
+    bool inCanvasSubtree = lineageOfType<HTMLCanvasElement>(*node->parentElement()).first();
+    bool isHidden = isNodeAriaVisible(node);
 
     bool insideMeterElement = false;
 #if ENABLE(METER_ELEMENT)
-    insideMeterElement = isHTMLMeterElement(node->parentElement());
+    insideMeterElement = is<HTMLMeterElement>(*node->parentElement());
 #endif
     
     if (!inCanvasSubtree && !isHidden && !insideMeterElement)
-        return 0;
+        return nullptr;
+
+    // Fallback content is only focusable as long as the canvas is displayed and visible.
+    // Update the style before Element::isFocusable() gets called.
+    if (inCanvasSubtree)
+        node->document().updateStyleIfNeeded();
 
     RefPtr<AccessibilityObject> newObj = createFromNode(node);
 
@@ -384,14 +513,18 @@ AccessibilityObject* AXObjectCache::getOrCreate(Node* node)
     newObj->init();
     attachWrapper(newObj.get());
     newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored());
-
+    // Sometimes asking accessibilityIsIgnored() will cause the newObject to be deallocated, and then
+    // it will disappear when this function is finished, leading to a use-after-free.
+    if (newObj->isDetached())
+        return nullptr;
+    
     return newObj.get();
 }
 
 AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer)
 {
     if (!renderer)
-        return 0;
+        return nullptr;
 
     if (AccessibilityObject* obj = get(renderer))
         return obj;
@@ -408,14 +541,18 @@ AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer)
     newObj->init();
     attachWrapper(newObj.get());
     newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored());
-
+    // Sometimes asking accessibilityIsIgnored() will cause the newObject to be deallocated, and then
+    // it will disappear when this function is finished, leading to a use-after-free.
+    if (newObj->isDetached())
+        return nullptr;
+    
     return newObj.get();
 }
     
 AccessibilityObject* AXObjectCache::rootObject()
 {
     if (!gAccessibilityEnabled)
-        return 0;
+        return nullptr;
 
     return getOrCreate(m_document.view());
 }
@@ -423,16 +560,16 @@ AccessibilityObject* AXObjectCache::rootObject()
 AccessibilityObject* AXObjectCache::rootObjectForFrame(Frame* frame)
 {
     if (!gAccessibilityEnabled)
-        return 0;
+        return nullptr;
 
     if (!frame)
-        return 0;
+        return nullptr;
     return getOrCreate(frame->view());
 }    
     
 AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role)
 {
-    RefPtr<AccessibilityObject> obj = 0;
+    RefPtr<AccessibilityObject> obj = nullptr;
     
     // will be filled in...
     switch (role) {
@@ -464,13 +601,13 @@ AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role)
         obj = AccessibilitySpinButtonPart::create();
         break;
     default:
-        obj = 0;
+        obj = nullptr;
     }
     
     if (obj)
         getAXID(obj.get());
     else
-        return 0;
+        return nullptr;
 
     m_objects.set(obj->axObjectID(), obj);    
     obj->init();
@@ -521,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;
@@ -538,7 +681,7 @@ void AXObjectCache::remove(Widget* view)
 }
     
     
-#if !PLATFORM(WIN) || OS(WINCE)
+#if !PLATFORM(WIN)
 AXID AXObjectCache::platformGenerateAXID() const
 {
     static AXID lastUsedID = 0;
@@ -615,13 +758,51 @@ void AXObjectCache::updateCacheAfterNodeIsAttached(Node* node)
     get(node);
 }
 
-void AXObjectCache::childrenChanged(Node* node)
+void AXObjectCache::handleMenuOpened(Node* node)
 {
+    if (!node || !node->renderer() || !nodeHasRole(node, "menu"))
+        return;
+    
+    postNotification(getOrCreate(node), &document(), AXMenuOpened);
+}
+    
+void AXObjectCache::handleLiveRegionCreated(Node* node)
+{
+    if (!is<Element>(node) || !node->renderer())
+        return;
+    
+    Element* element = downcast<Element>(node);
+    String liveRegionStatus = element->fastGetAttribute(aria_liveAttr);
+    if (liveRegionStatus.isEmpty()) {
+        const AtomicString& ariaRole = element->fastGetAttribute(roleAttr);
+        if (!ariaRole.isEmpty())
+            liveRegionStatus = AccessibilityObject::defaultLiveRegionStatusForRole(AccessibilityObject::ariaRoleToWebCoreRole(ariaRole));
+    }
+    
+    if (AccessibilityObject::liveRegionStatusIsEnabled(liveRegionStatus))
+        postNotification(getOrCreate(node), &document(), AXLiveRegionCreated);
+}
+    
+void AXObjectCache::childrenChanged(Node* node, Node* newChild)
+{
+    if (newChild) {
+        handleMenuOpened(newChild);
+        handleLiveRegionCreated(newChild);
+    }
+    
     childrenChanged(get(node));
 }
 
-void AXObjectCache::childrenChanged(RenderObject* renderer)
+void AXObjectCache::childrenChanged(RenderObject* renderer, RenderObject* newChild)
 {
+    if (!renderer)
+        return;
+    
+    if (newChild) {
+        handleMenuOpened(newChild->node());
+        handleLiveRegionCreated(newChild->node());
+    }
+    
     childrenChanged(get(renderer));
 }
 
@@ -633,15 +814,17 @@ void AXObjectCache::childrenChanged(AccessibilityObject* obj)
     obj->childrenChanged();
 }
     
-void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>&)
+void AXObjectCache::notificationPostTimerFired()
 {
     Ref<Document> protectorForCacheOwner(m_document);
-
     m_notificationPostTimer.stop();
-
-    unsigned i = 0, count = m_notificationsToPost.size();
-    for (i = 0; i < count; ++i) {
-        AccessibilityObject* obj = m_notificationsToPost[i].first.get();
+    
+    // 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 = WTFMove(m_notificationsToPost);
+    
+    for (const auto& note : notifications) {
+        AccessibilityObject* obj = note.first.get();
         if (!obj->axObjectID())
             continue;
 
@@ -651,22 +834,41 @@ void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>&)
 #ifndef NDEBUG
         // Make sure none of the render views are in the process of being layed out.
         // Notifications should only be sent after the renderer has finished
-        if (obj->isAccessibilityRenderObject()) {
-            AccessibilityRenderObject* renderObj = toAccessibilityRenderObject(obj);
-            RenderObject* renderer = renderObj->renderer();
-            if (renderer)
+        if (is<AccessibilityRenderObject>(*obj)) {
+            if (auto* renderer = downcast<AccessibilityRenderObject>(*obj).renderer())
                 ASSERT(!renderer->view().layoutState());
         }
 #endif
+
+        AXNotification notification = note.second;
+        
+        // Ensure that this menu really is a menu. We do this check here so that we don't have to create
+        // the axChildren when the menu is marked as opening.
+        if (notification == AXMenuOpened) {
+            obj->updateChildrenIfNecessary();
+            if (obj->roleValue() != MenuRole)
+                continue;
+        }
         
-        AXNotification notification = m_notificationsToPost[i].second;
         postPlatformNotification(obj, notification);
 
         if (notification == AXChildrenChanged && obj->parentObjectIfExists() && obj->lastKnownIsIgnoredValue() != obj->accessibilityIsIgnored())
             childrenChanged(obj->parentObject());
     }
-    
-    m_notificationsToPost.clear();
+}
+
+void AXObjectCache::passwordNotificationPostTimerFired()
+{
+#if PLATFORM(COCOA)
+    m_passwordNotificationPostTimer.stop();
+
+    // 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 = WTFMove(m_passwordNotificationsToPost);
+
+    for (auto& notification : notifications)
+        postTextStateChangePlatformNotification(notification.get(), AXTextEditTypeInsert, " ", VisiblePosition());
+#endif
 }
     
 void AXObjectCache::postNotification(RenderObject* renderer, AXNotification notification, PostTarget postTarget, PostType postType)
@@ -737,8 +939,30 @@ void AXObjectCache::checkedStateChanged(Node* node)
     postNotification(node, AXObjectCache::AXCheckedStateChanged);
 }
 
+void AXObjectCache::handleMenuItemSelected(Node* node)
+{
+    if (!node)
+        return;
+    
+    if (!nodeHasRole(node, "menuitem") && !nodeHasRole(node, "menuitemradio") && !nodeHasRole(node, "menuitemcheckbox"))
+        return;
+    
+    if (!downcast<Element>(*node).focused() && !equalIgnoringCase(downcast<Element>(*node).fastGetAttribute(aria_selectedAttr), "true"))
+        return;
+    
+    postNotification(getOrCreate(node), &document(), AXMenuListItemSelected);
+}
+    
+void AXObjectCache::handleFocusedUIElementChanged(Node* oldNode, Node* newNode)
+{
+    handleMenuItemSelected(newNode);
+    platformHandleFocusedUIElementChanged(oldNode, newNode);
+}
+    
 void AXObjectCache::selectedChildrenChanged(Node* node)
 {
+    handleMenuItemSelected(node);
+    
     // postTarget is TargetObservableParent so that you can pass in any child of an element and it will go up the parent tree
     // to find the container which should send out the notification.
     postNotification(node, AXSelectedChildrenChanged, TargetObservableParent);
@@ -746,21 +970,274 @@ void AXObjectCache::selectedChildrenChanged(Node* node)
 
 void AXObjectCache::selectedChildrenChanged(RenderObject* renderer)
 {
+    if (renderer)
+        handleMenuItemSelected(renderer->node());
+
     // postTarget is TargetObservableParent so that you can pass in any child of an element and it will go up the parent tree
     // to find the container which should send out the notification.
     postNotification(renderer, AXSelectedChildrenChanged, TargetObservableParent);
 }
 
-void AXObjectCache::nodeTextChangeNotification(Node* node, AXTextChange textChange, unsigned offset, const String& text)
+#ifndef NDEBUG
+void AXObjectCache::showIntent(const AXTextStateChangeIntent &intent)
+{
+    switch (intent.type) {
+    case AXTextStateChangeTypeUnknown:
+        dataLog("Unknown");
+        break;
+    case AXTextStateChangeTypeEdit:
+        dataLog("Edit::");
+        break;
+    case AXTextStateChangeTypeSelectionMove:
+        dataLog("Move::");
+        break;
+    case AXTextStateChangeTypeSelectionExtend:
+        dataLog("Extend::");
+        break;
+    }
+    switch (intent.type) {
+    case AXTextStateChangeTypeUnknown:
+        break;
+    case AXTextStateChangeTypeEdit:
+        switch (intent.change) {
+        case AXTextEditTypeUnknown:
+            dataLog("Unknown");
+            break;
+        case AXTextEditTypeDelete:
+            dataLog("Delete");
+            break;
+        case AXTextEditTypeInsert:
+            dataLog("Insert");
+            break;
+        case AXTextEditTypeDictation:
+            dataLog("DictationInsert");
+            break;
+        case AXTextEditTypeTyping:
+            dataLog("TypingInsert");
+            break;
+        case AXTextEditTypeCut:
+            dataLog("Cut");
+            break;
+        case AXTextEditTypePaste:
+            dataLog("Paste");
+            break;
+        case AXTextEditTypeAttributesChange:
+            dataLog("AttributesChange");
+            break;
+        }
+        break;
+    case AXTextStateChangeTypeSelectionMove:
+    case AXTextStateChangeTypeSelectionExtend:
+        switch (intent.selection.direction) {
+        case AXTextSelectionDirectionUnknown:
+            dataLog("Unknown::");
+            break;
+        case AXTextSelectionDirectionBeginning:
+            dataLog("Beginning::");
+            break;
+        case AXTextSelectionDirectionEnd:
+            dataLog("End::");
+            break;
+        case AXTextSelectionDirectionPrevious:
+            dataLog("Previous::");
+            break;
+        case AXTextSelectionDirectionNext:
+            dataLog("Next::");
+            break;
+        case AXTextSelectionDirectionDiscontiguous:
+            dataLog("Discontiguous::");
+            break;
+        }
+        switch (intent.selection.direction) {
+        case AXTextSelectionDirectionUnknown:
+        case AXTextSelectionDirectionBeginning:
+        case AXTextSelectionDirectionEnd:
+        case AXTextSelectionDirectionPrevious:
+        case AXTextSelectionDirectionNext:
+            switch (intent.selection.granularity) {
+            case AXTextSelectionGranularityUnknown:
+                dataLog("Unknown");
+                break;
+            case AXTextSelectionGranularityCharacter:
+                dataLog("Character");
+                break;
+            case AXTextSelectionGranularityWord:
+                dataLog("Word");
+                break;
+            case AXTextSelectionGranularityLine:
+                dataLog("Line");
+                break;
+            case AXTextSelectionGranularitySentence:
+                dataLog("Sentence");
+                break;
+            case AXTextSelectionGranularityParagraph:
+                dataLog("Paragraph");
+                break;
+            case AXTextSelectionGranularityPage:
+                dataLog("Page");
+                break;
+            case AXTextSelectionGranularityDocument:
+                dataLog("Document");
+                break;
+            case AXTextSelectionGranularityAll:
+                dataLog("All");
+                break;
+            }
+            break;
+        case AXTextSelectionDirectionDiscontiguous:
+            break;
+        }
+        break;
+    }
+    dataLog("\n");
+}
+#endif
+
+void AXObjectCache::setTextSelectionIntent(const AXTextStateChangeIntent& intent)
+{
+    m_textSelectionIntent = intent;
+}
+    
+void AXObjectCache::setIsSynchronizingSelection(bool isSynchronizing)
+{
+    m_isSynchronizingSelection = isSynchronizing;
+}
+
+static bool isPasswordFieldOrContainedByPasswordField(AccessibilityObject* object)
+{
+    return object && (object->isPasswordField() || object->isContainedByPasswordField());
+}
+
+void AXObjectCache::postTextStateChangeNotification(Node* node, const AXTextStateChangeIntent& intent, const VisibleSelection& selection)
 {
     if (!node)
         return;
 
+#if PLATFORM(COCOA)
     stopCachingComputedObjectAttributes();
 
-    // Delegate on the right platform
-    AccessibilityObject* obj = getOrCreate(node);
-    nodeTextChangePlatformNotification(obj, textChange, offset, text);
+    postTextStateChangeNotification(getOrCreate(node), intent, selection);
+#else
+    postNotification(node->renderer(), AXObjectCache::AXSelectedTextChanged, TargetObservableParent);
+    UNUSED_PARAM(intent);
+    UNUSED_PARAM(selection);
+#endif
+}
+
+void AXObjectCache::postTextStateChangeNotification(const Position& position, const AXTextStateChangeIntent& intent, const VisibleSelection& selection)
+{
+    Node* node = position.deprecatedNode();
+    if (!node)
+        return;
+
+    stopCachingComputedObjectAttributes();
+
+#if PLATFORM(COCOA)
+    AccessibilityObject* object = getOrCreate(node);
+    if (object && object->accessibilityIsIgnored()) {
+        if (position.atLastEditingPositionForNode()) {
+            if (AccessibilityObject* nextSibling = object->nextSiblingUnignored(1))
+                object = nextSibling;
+        } else if (position.atFirstEditingPositionForNode()) {
+            if (AccessibilityObject* previousSibling = object->previousSiblingUnignored(1))
+                object = previousSibling;
+        }
+    }
+
+    postTextStateChangeNotification(object, intent, selection);
+#else
+    postTextStateChangeNotification(node, intent, selection);
+#endif
+}
+
+void AXObjectCache::postTextStateChangeNotification(AccessibilityObject* object, const AXTextStateChangeIntent& intent, const VisibleSelection& selection)
+{
+    stopCachingComputedObjectAttributes();
+
+#if PLATFORM(COCOA)
+    if (object) {
+        if (isPasswordFieldOrContainedByPasswordField(object))
+            return;
+
+        if (auto observableObject = object->observableObject())
+            object = observableObject;
+    }
+
+    const AXTextStateChangeIntent& newIntent = (intent.type == AXTextStateChangeTypeUnknown || (m_isSynchronizingSelection && m_textSelectionIntent.type != AXTextStateChangeTypeUnknown)) ? m_textSelectionIntent : intent;
+    postTextStateChangePlatformNotification(object, newIntent, selection);
+#else
+    UNUSED_PARAM(object);
+    UNUSED_PARAM(intent);
+    UNUSED_PARAM(selection);
+#endif
+
+    setTextSelectionIntent(AXTextStateChangeIntent());
+    setIsSynchronizingSelection(false);
+}
+
+void AXObjectCache::postTextStateChangeNotification(Node* node, AXTextEditType type, const String& text, const VisiblePosition& position)
+{
+    if (!node)
+        return;
+    ASSERT(type != AXTextEditTypeUnknown);
+
+    stopCachingComputedObjectAttributes();
+
+    AccessibilityObject* object = getOrCreate(node);
+#if PLATFORM(COCOA)
+    if (object) {
+        if (enqueuePasswordValueChangeNotification(object))
+            return;
+        object = object->observableObject();
+    }
+
+    postTextStateChangePlatformNotification(object, type, text, position);
+#else
+    nodeTextChangePlatformNotification(object, textChangeForEditType(type), position.deepEquivalent().deprecatedEditingOffset(), text);
+#endif
+}
+
+void AXObjectCache::postTextReplacementNotification(Node* node, AXTextEditType deletionType, const String& deletedText, AXTextEditType insertionType, const String& insertedText, const VisiblePosition& position)
+{
+    if (!node)
+        return;
+    ASSERT(deletionType == AXTextEditTypeDelete);
+    ASSERT(insertionType == AXTextEditTypeInsert || insertionType == AXTextEditTypeTyping || insertionType == AXTextEditTypeDictation || insertionType == AXTextEditTypePaste);
+
+    stopCachingComputedObjectAttributes();
+
+    AccessibilityObject* object = getOrCreate(node);
+#if PLATFORM(COCOA)
+    if (object) {
+        if (enqueuePasswordValueChangeNotification(object))
+            return;
+        object = object->observableObject();
+    }
+
+    postTextReplacementPlatformNotification(object, deletionType, deletedText, insertionType, insertedText, position);
+#else
+    nodeTextChangePlatformNotification(object, textChangeForEditType(deletionType), position.deepEquivalent().deprecatedEditingOffset(), deletedText);
+    nodeTextChangePlatformNotification(object, textChangeForEditType(insertionType), position.deepEquivalent().deprecatedEditingOffset(), insertedText);
+#endif
+}
+
+bool AXObjectCache::enqueuePasswordValueChangeNotification(AccessibilityObject* object)
+{
+    if (!isPasswordFieldOrContainedByPasswordField(object))
+        return false;
+
+    AccessibilityObject* observableObject = object->observableObject();
+    if (!observableObject) {
+        ASSERT_NOT_REACHED();
+        // return true even though the enqueue didn't happen because this is a password field and caller shouldn't post a notification
+        return true;
+    }
+
+    m_passwordNotificationsToPost.add(observableObject);
+    if (!m_passwordNotificationPostTimer.isActive())
+        m_passwordNotificationPostTimer.startOneShot(AccessibilityPasswordValueChangeNotificationInterval);
+
+    return true;
 }
 
 void AXObjectCache::frameLoadingEventNotification(Frame* frame, AXLoadingEvent loadingEvent)
@@ -777,6 +1254,29 @@ void AXObjectCache::frameLoadingEventNotification(Frame* frame, AXLoadingEvent l
     frameLoadingEventPlatformNotification(obj, loadingEvent);
 }
 
+void AXObjectCache::postLiveRegionChangeNotification(AccessibilityObject* object)
+{
+    if (m_liveRegionChangedPostTimer.isActive())
+        m_liveRegionChangedPostTimer.stop();
+
+    if (!m_liveRegionObjectsSet.contains(object))
+        m_liveRegionObjectsSet.add(object);
+
+    m_liveRegionChangedPostTimer.startOneShot(AccessibilityLiveRegionChangedNotificationInterval);
+}
+
+void AXObjectCache::liveRegionChangedNotificationPostTimerFired()
+{
+    m_liveRegionChangedPostTimer.stop();
+
+    if (m_liveRegionObjectsSet.isEmpty())
+        return;
+
+    for (auto& object : m_liveRegionObjectsSet)
+        postNotification(object.get(), object->document(), AXObjectCache::AXLiveRegionChanged);
+    m_liveRegionObjectsSet.clear();
+}
+
 void AXObjectCache::handleScrollbarUpdate(ScrollView* view)
 {
     if (!view)
@@ -817,7 +1317,7 @@ void AXObjectCache::handleAttributeChanged(const QualifiedName& attrName, Elemen
         handleAriaRoleChanged(element);
     else if (attrName == altAttr || attrName == titleAttr)
         textChanged(element);
-    else if (attrName == forAttr && isHTMLLabelElement(element))
+    else if (attrName == forAttr && is<HTMLLabelElement>(*element))
         labelChanged(element);
 
     if (!attrName.localName().string().startsWith("aria-"))
@@ -838,17 +1338,41 @@ void AXObjectCache::handleAttributeChanged(const QualifiedName& attrName, Elemen
     else if (attrName == aria_expandedAttr)
         handleAriaExpandedChange(element);
     else if (attrName == aria_hiddenAttr)
-        childrenChanged(element->parentNode());
+        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(isHTMLLabelElement(element));
-    HTMLElement* correspondingControl = toHTMLLabelElement(element)->control();
+    ASSERT(is<HTMLLabelElement>(*element));
+    HTMLElement* correspondingControl = downcast<HTMLLabelElement>(*element).control();
     textChanged(correspondingControl);
 }
 
@@ -909,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();
@@ -930,7 +1451,7 @@ void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerD
 const Element* AXObjectCache::rootAXEditableElement(const Node* node)
 {
     const Element* result = node->rootEditableElement();
-    const Element* element = node->isElementNode() ? toElement(node) : node->parentElement();
+    const Element* element = is<Element>(*node) ? downcast<Element>(node) : node->parentElement();
 
     for (; element; element = element->parentElement()) {
         if (nodeIsTextControl(element))
@@ -942,20 +1463,18 @@ const Element* AXObjectCache::rootAXEditableElement(const Node* node)
 
 void AXObjectCache::clearTextMarkerNodesInUse(Document* document)
 {
-    HashSet<Node*>::iterator it = m_textMarkerNodes.begin();
-    HashSet<Node*>::iterator end = m_textMarkerNodes.end();
-
-    // Check each node to see if it's inside the document being deleted.
+    if (!document)
+        return;
+    
+    // Check each node to see if it's inside the document being deleted, of if it no longer belongs to a document.
     HashSet<Node*> nodesToDelete;
-    for (; it != end; ++it) {
-        if (&(*it)->document() == document)
-            nodesToDelete.add(*it);
+    for (const auto& node : m_textMarkerNodes) {
+        if (!node->inDocument() || &(node)->document() == document)
+            nodesToDelete.add(node);
     }
     
-    it = nodesToDelete.begin();
-    end = nodesToDelete.end();
-    for (; it != end; ++it)
-        m_textMarkerNodes.remove(*it);
+    for (const auto& node : nodesToDelete)
+        m_textMarkerNodes.remove(node);
 }
     
 bool AXObjectCache::nodeIsTextControl(const Node* node)
@@ -971,20 +1490,38 @@ bool isNodeAriaVisible(Node* node)
 {
     if (!node)
         return false;
-    
-    // To determine if a node is ARIA visible, we need to check the parent hierarchy to see if anyone specifies
-    // aria-hidden explicitly.
+
+    // ARIA Node visibility is controlled by aria-hidden
+    //  1) if aria-hidden=true, the whole subtree is hidden
+    //  2) if aria-hidden=false, and the object is rendered, there's no effect
+    //  3) if aria-hidden=false, and the object is NOT rendered, then it must have
+    //     aria-hidden=false on each parent until it gets to a rendered object
+    //  3b) a text node inherits a parents aria-hidden value
+    bool requiresAriaHiddenFalse = !node->renderer();
+    bool ariaHiddenFalsePresent = false;
     for (Node* testNode = node; testNode; testNode = testNode->parentNode()) {
-        if (testNode->isElementNode()) {
-            const AtomicString& ariaHiddenValue = toElement(testNode)->fastGetAttribute(aria_hiddenAttr);
-            if (equalIgnoringCase(ariaHiddenValue, "false"))
-                return true;
+        if (is<Element>(*testNode)) {
+            const AtomicString& ariaHiddenValue = downcast<Element>(*testNode).fastGetAttribute(aria_hiddenAttr);
             if (equalIgnoringCase(ariaHiddenValue, "true"))
                 return false;
+            
+            bool ariaHiddenFalse = equalIgnoringCase(ariaHiddenValue, "false");
+            if (!testNode->renderer() && !ariaHiddenFalse)
+                return false;
+            if (!ariaHiddenFalsePresent && ariaHiddenFalse)
+                ariaHiddenFalsePresent = true;
         }
     }
     
-    return false;
+    return !requiresAriaHiddenFalse || ariaHiddenFalsePresent;
+}
+
+AccessibilityObject* AXObjectCache::rootWebArea()
+{
+    AccessibilityObject* rootObject = this->rootObject();
+    if (!rootObject || !rootObject->isAccessibilityScrollView())
+        return nullptr;
+    return downcast<AccessibilityScrollView>(*rootObject).webAreaObject();
 }
 
 AXAttributeCacheEnabler::AXAttributeCacheEnabler(AXObjectCache* cache)
@@ -999,6 +1536,28 @@ AXAttributeCacheEnabler::~AXAttributeCacheEnabler()
     if (m_cache)
         m_cache->stopCachingComputedObjectAttributes();
 }
+
+#if !PLATFORM(COCOA)
+AXTextChange AXObjectCache::textChangeForEditType(AXTextEditType type)
+{
+    switch (type) {
+    case AXTextEditTypeCut:
+    case AXTextEditTypeDelete:
+        return AXTextDeleted;
+    case AXTextEditTypeInsert:
+    case AXTextEditTypeDictation:
+    case AXTextEditTypeTyping:
+    case AXTextEditTypePaste:
+        return AXTextInserted;
+    case AXTextEditTypeAttributesChange:
+        return AXTextAttributesChanged;
+    case AXTextEditTypeUnknown:
+        break;
+    }
+    ASSERT_NOT_REACHED();
+    return AXTextInserted;
+}
+#endif
     
 } // namespace WebCore