Replace WTF::move with WTFMove
[WebKit-https.git] / Source / WebCore / accessibility / AXObjectCache.cpp
index eaa1623..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
@@ -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"
@@ -56,6 +55,8 @@
 #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 "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"
@@ -89,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);
@@ -123,17 +129,28 @@ void AXObjectCache::disableAccessibility()
 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);
@@ -142,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
@@ -158,10 +260,10 @@ AccessibilityObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* ar
         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();
     }    
     
@@ -176,7 +278,7 @@ AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page)
     // get the focused node in the page
     Document* focusedDocument = page->focusController().focusedOrMainFrame().document();
     Element* focusedElement = focusedDocument->focusedElement();
-    if (focusedElement && isHTMLAreaElement(focusedElement))
+    if (is<HTMLAreaElement>(focusedElement))
         return focusedImageMapUIElement(downcast<HTMLAreaElement>(focusedElement));
 
     AccessibilityObject* obj = focusedDocument->axObjectCache()->getOrCreate(focusedElement ? static_cast<Node*>(focusedElement) : focusedDocument);
@@ -253,10 +355,10 @@ 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;
 
-    auto& roleValue = toElement(node)->fastGetAttribute(roleAttr);
+    auto& roleValue = downcast<Element>(*node).fastGetAttribute(roleAttr);
     if (role.isNull())
         return roleValue.isEmpty();
     if (roleValue.isEmpty())
@@ -265,7 +367,7 @@ bool nodeHasRole(Node* node, const String& role)
     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();
@@ -277,59 +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 (renderer->isSVGRoot())
+    if (is<RenderSVGRoot>(*renderer))
         return AccessibilitySVGRoot::create(renderer);
     
-    // 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 (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));
+        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);
 }
@@ -342,11 +446,11 @@ AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget)
     if (AccessibilityObject* obj = get(widget))
         return obj;
     
-    RefPtr<AccessibilityObject> newObj = nullptr;
-    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));
@@ -386,12 +490,17 @@ AccessibilityObject* AXObjectCache::getOrCreate(Node* node)
 
     bool insideMeterElement = false;
 #if ENABLE(METER_ELEMENT)
-    insideMeterElement = isHTMLMeterElement(node->parentElement());
+    insideMeterElement = is<HTMLMeterElement>(*node->parentElement());
 #endif
     
     if (!inCanvasSubtree && !isHidden && !insideMeterElement)
         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);
 
     // Will crash later if we have two objects for the same node.
@@ -549,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;
@@ -566,7 +681,7 @@ void AXObjectCache::remove(Widget* view)
 }
     
     
-#if !PLATFORM(WIN) || OS(WINCE)
+#if !PLATFORM(WIN)
 AXID AXObjectCache::platformGenerateAXID() const
 {
     static AXID lastUsedID = 0;
@@ -653,10 +768,10 @@ void AXObjectCache::handleMenuOpened(Node* node)
     
 void AXObjectCache::handleLiveRegionCreated(Node* node)
 {
-    if (!node || !node->renderer() || !node->isElementNode())
+    if (!is<Element>(node) || !node->renderer())
         return;
     
-    Element* element = toElement(node);
+    Element* element = downcast<Element>(node);
     String liveRegionStatus = element->fastGetAttribute(aria_liveAttr);
     if (liveRegionStatus.isEmpty()) {
         const AtomicString& ariaRole = element->fastGetAttribute(roleAttr);
@@ -699,14 +814,14 @@ void AXObjectCache::childrenChanged(AccessibilityObject* obj)
     obj->childrenChanged();
 }
     
-void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>&)
+void AXObjectCache::notificationPostTimerFired()
 {
     Ref<Document> protectorForCacheOwner(m_document);
     m_notificationPostTimer.stop();
     
-    // In DRT, posting notifications has a tendency to immediately queue up other notifications, which can lead to unexpected behavior
+    // 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();
@@ -719,10 +834,8 @@ 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
@@ -743,6 +856,20 @@ void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>&)
             childrenChanged(obj->parentObject());
     }
 }
+
+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)
 {
@@ -820,7 +947,7 @@ void AXObjectCache::handleMenuItemSelected(Node* node)
     if (!nodeHasRole(node, "menuitem") && !nodeHasRole(node, "menuitemradio") && !nodeHasRole(node, "menuitemcheckbox"))
         return;
     
-    if (!toElement(node)->focused() && !equalIgnoringCase(toElement(node)->fastGetAttribute(aria_selectedAttr), "true"))
+    if (!downcast<Element>(*node).focused() && !equalIgnoringCase(downcast<Element>(*node).fastGetAttribute(aria_selectedAttr), "true"))
         return;
     
     postNotification(getOrCreate(node), &document(), AXMenuListItemSelected);
@@ -851,16 +978,266 @@ void AXObjectCache::selectedChildrenChanged(RenderObject* renderer)
     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)
@@ -877,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)
@@ -917,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-"))
@@ -941,14 +1341,38 @@ 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(isHTMLLabelElement(element));
-    HTMLElement* correspondingControl = downcast<HTMLLabelElement>(element)->control();
+    ASSERT(is<HTMLLabelElement>(*element));
+    HTMLElement* correspondingControl = downcast<HTMLLabelElement>(*element).control();
     textChanged(correspondingControl);
 }
 
@@ -1009,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();
@@ -1030,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))
@@ -1069,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)
@@ -1097,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