AX: Defer attribute computation until needed.
[WebKit-https.git] / Source / WebCore / accessibility / AccessibilityNodeObject.cpp
index 58c851f..b072efd 100644 (file)
@@ -35,6 +35,8 @@
 #include "AccessibilityListBox.h"
 #include "AccessibilitySpinButton.h"
 #include "AccessibilityTable.h"
+#include "AccessibleNode.h"
+#include "Editing.h"
 #include "ElementIterator.h"
 #include "EventNames.h"
 #include "FloatRect.h"
 #include "FrameLoader.h"
 #include "FrameSelection.h"
 #include "FrameView.h"
-#include "HTMLAreaElement.h"
 #include "HTMLCanvasElement.h"
+#include "HTMLDetailsElement.h"
 #include "HTMLFieldSetElement.h"
 #include "HTMLFormElement.h"
-#include "HTMLFrameElementBase.h"
 #include "HTMLImageElement.h"
 #include "HTMLInputElement.h"
 #include "HTMLLabelElement.h"
 #include "HTMLLegendElement.h"
-#include "HTMLMapElement.h"
 #include "HTMLNames.h"
-#include "HTMLOptGroupElement.h"
-#include "HTMLOptionElement.h"
-#include "HTMLOptionsCollection.h"
 #include "HTMLParserIdioms.h"
-#include "HTMLPlugInImageElement.h"
 #include "HTMLSelectElement.h"
 #include "HTMLTextAreaElement.h"
 #include "HTMLTextFormControlElement.h"
-#include "HitTestRequest.h"
-#include "HitTestResult.h"
 #include "LabelableElement.h"
 #include "LocalizedStrings.h"
 #include "MathMLElement.h"
 #include "MathMLNames.h"
 #include "NodeList.h"
 #include "NodeTraversal.h"
-#include "Page.h"
 #include "ProgressTracker.h"
 #include "RenderImage.h"
 #include "RenderView.h"
 #include "SVGElement.h"
-#include "SVGNames.h"
 #include "Text.h"
 #include "TextControlInnerElements.h"
 #include "UserGestureIndicator.h"
 #include "VisibleUnits.h"
 #include "Widget.h"
-#include "htmlediting.h"
 #include <wtf/StdLibExtras.h>
 #include <wtf/text/StringBuilder.h>
 #include <wtf/unicode/CharacterNames.h>
@@ -93,12 +84,6 @@ static String accessibleNameForNode(Node* node, Node* labelledbyNode = nullptr);
 
 AccessibilityNodeObject::AccessibilityNodeObject(Node* node)
     : AccessibilityObject()
-    , m_ariaRole(UnknownRole)
-    , m_childrenDirty(false)
-    , m_roleForMSAA(UnknownRole)
-#ifndef NDEBUG
-    , m_initialized(false)
-#endif
     , m_node(node)
 {
 }
@@ -139,24 +124,38 @@ void AccessibilityNodeObject::childrenChanged()
     if (!cache)
         return;
     cache->postNotification(this, document(), AXObjectCache::AXChildrenChanged);
+    
+    // Should make the sub tree dirty so that everything below will be updated correctly.
+    this->setNeedsToUpdateSubtree();
+    bool shouldStopUpdatingParent = false;
 
     // Go up the accessibility parent chain, but only if the element already exists. This method is
     // called during render layouts, minimal work should be done. 
     // If AX elements are created now, they could interrogate the render tree while it's in a funky state.
     // At the same time, process ARIA live region changes.
     for (AccessibilityObject* parent = this; parent; parent = parent->parentObjectIfExists()) {
-        parent->setNeedsToUpdateChildren();
+        if (!shouldStopUpdatingParent)
+            parent->setNeedsToUpdateChildren();
+        
 
         // These notifications always need to be sent because screenreaders are reliant on them to perform. 
         // In other words, they need to be sent even when the screen reader has not accessed this live region since the last update.
 
         // If this element supports ARIA live regions, then notify the AT of changes.
-        if (parent->supportsARIALiveRegion())
-            cache->postNotification(parent, parent->document(), AXObjectCache::AXLiveRegionChanged);
+        // Sometimes this function can be called many times within a short period of time, leading to posting too many AXLiveRegionChanged
+        // notifications. To fix this, we used a timer to make sure we only post one notification for the children changes within a pre-defined
+        // time interval.
+        if (parent->supportsLiveRegion())
+            cache->postLiveRegionChangeNotification(parent);
         
         // If this element is an ARIA text control, notify the AT of changes.
-        if ((parent->isARIATextControl() || parent->hasContentEditableAttributeSet()) && !parent->isNativeTextControl())
+        if (parent->isNonNativeTextControl()) {
             cache->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged);
+            
+            // Do not let the parent that's above the editable ancestor update its children
+            // since we already notify the AT of changes.
+            shouldStopUpdatingParent = true;
+        }
     }
 }
 
@@ -243,7 +242,7 @@ LayoutRect AccessibilityNodeObject::elementRect() const
 {
     return boundingBoxRect();
 }
-    
+
 LayoutRect AccessibilityNodeObject::boundingBoxRect() const
 {
     // AccessibilityNodeObjects have no mechanism yet to return a size or position.
@@ -279,86 +278,57 @@ Document* AccessibilityNodeObject::document() const
 AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole()
 {
     if (!node())
-        return UnknownRole;
+        return AccessibilityRole::Unknown;
 
-    if ((m_ariaRole = determineAriaRoleAttribute()) != UnknownRole)
+    if ((m_ariaRole = determineAriaRoleAttribute()) != AccessibilityRole::Unknown)
         return m_ariaRole;
     
     if (node()->isLink())
-        return WebCoreLinkRole;
+        return AccessibilityRole::WebCoreLink;
     if (node()->isTextNode())
-        return StaticTextRole;
+        return AccessibilityRole::StaticText;
     if (node()->hasTagName(buttonTag))
         return buttonRoleType();
     if (is<HTMLInputElement>(*node())) {
         HTMLInputElement& input = downcast<HTMLInputElement>(*node());
         if (input.isCheckbox())
-            return CheckBoxRole;
+            return AccessibilityRole::CheckBox;
         if (input.isRadioButton())
-            return RadioButtonRole;
+            return AccessibilityRole::RadioButton;
         if (input.isTextButton())
             return buttonRoleType();
         if (input.isRangeControl())
-            return SliderRole;
+            return AccessibilityRole::Slider;
         if (input.isInputTypeHidden())
-            return IgnoredRole;
+            return AccessibilityRole::Ignored;
         if (input.isSearchField())
-            return SearchFieldRole;
-        
+            return AccessibilityRole::SearchField;
 #if ENABLE(INPUT_TYPE_COLOR)
-        const AtomicString& type = input.getAttribute(typeAttr);
-        if (equalIgnoringCase(type, "color"))
-            return ColorWellRole;
+        if (input.isColorControl())
+            return AccessibilityRole::ColorWell;
 #endif
-
-        return TextFieldRole;
+        return AccessibilityRole::TextField;
     }
     if (node()->hasTagName(selectTag)) {
         HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*node());
-        return selectElement.multiple() ? ListBoxRole : PopUpButtonRole;
+        return selectElement.multiple() ? AccessibilityRole::ListBox : AccessibilityRole::PopUpButton;
     }
     if (is<HTMLTextAreaElement>(*node()))
-        return TextAreaRole;
+        return AccessibilityRole::TextArea;
     if (headingLevel())
-        return HeadingRole;
+        return AccessibilityRole::Heading;
     if (node()->hasTagName(blockquoteTag))
-        return BlockquoteRole;
+        return AccessibilityRole::Blockquote;
     if (node()->hasTagName(divTag))
-        return DivRole;
+        return AccessibilityRole::Div;
     if (node()->hasTagName(pTag))
-        return ParagraphRole;
+        return AccessibilityRole::Paragraph;
     if (is<HTMLLabelElement>(*node()))
-        return LabelRole;
+        return AccessibilityRole::Label;
     if (is<Element>(*node()) && downcast<Element>(*node()).isFocusable())
-        return GroupRole;
+        return AccessibilityRole::Group;
     
-    return UnknownRole;
-}
-
-void AccessibilityNodeObject::insertChild(AccessibilityObject* child, unsigned index)
-{
-    if (!child)
-        return;
-    
-    // If the parent is asking for this child's children, then either it's the first time (and clearing is a no-op),
-    // or its visibility has changed. In the latter case, this child may have a stale child cached.
-    // This can prevent aria-hidden changes from working correctly. Hence, whenever a parent is getting children, ensure data is not stale.
-    child->clearChildren();
-    
-    if (child->accessibilityIsIgnored()) {
-        const auto& children = child->children();
-        size_t length = children.size();
-        for (size_t i = 0; i < length; ++i)
-            m_children.insert(index + i, children[i]);
-    } else {
-        ASSERT(child->parentObject() == this);
-        m_children.insert(index, child);
-    }
-}
-
-void AccessibilityNodeObject::addChild(AccessibilityObject* child)
-{
-    insertChild(child, m_children.size());
+    return AccessibilityRole::Unknown;
 }
 
 void AccessibilityNodeObject::addChildren()
@@ -378,6 +348,8 @@ void AccessibilityNodeObject::addChildren()
     
     for (Node* child = m_node->firstChild(); child; child = child->nextSibling())
         addChild(axObjectCache()->getOrCreate(child));
+    
+    m_subtreeDirty = false;
 }
 
 bool AccessibilityNodeObject::canHaveChildren() const
@@ -394,18 +366,26 @@ bool AccessibilityNodeObject::canHaveChildren() const
     
     // Elements that should not have children
     switch (roleValue()) {
-    case ImageRole:
-    case ButtonRole:
-    case PopUpButtonRole:
-    case CheckBoxRole:
-    case RadioButtonRole:
-    case TabRole:
-    case ToggleButtonRole:
-    case StaticTextRole:
-    case ListBoxOptionRole:
-    case ScrollBarRole:
-    case ProgressIndicatorRole:
-    case SwitchRole:
+    case AccessibilityRole::Image:
+    case AccessibilityRole::Button:
+    case AccessibilityRole::PopUpButton:
+    case AccessibilityRole::CheckBox:
+    case AccessibilityRole::RadioButton:
+    case AccessibilityRole::Tab:
+    case AccessibilityRole::ToggleButton:
+    case AccessibilityRole::StaticText:
+    case AccessibilityRole::ListBoxOption:
+    case AccessibilityRole::ScrollBar:
+    case AccessibilityRole::ProgressIndicator:
+    case AccessibilityRole::Switch:
+    case AccessibilityRole::MenuItemCheckbox:
+    case AccessibilityRole::MenuItemRadio:
+    case AccessibilityRole::Splitter:
+        return false;
+    case AccessibilityRole::DocumentMath:
+#if ENABLE(MATHML)
+        return node()->isMathMLElement();
+#endif
         return false;
     default:
         return true;
@@ -433,18 +413,18 @@ bool AccessibilityNodeObject::computeAccessibilityIsIgnored() const
     }
 
     AccessibilityObjectInclusion decision = defaultObjectInclusion();
-    if (decision == IncludeObject)
+    if (decision == AccessibilityObjectInclusion::IncludeObject)
         return false;
-    if (decision == IgnoreObject)
+    if (decision == AccessibilityObjectInclusion::IgnoreObject)
         return true;
     // If this element is within a parent that cannot have children, it should not be exposed.
     if (isDescendantOfBarrenParent())
         return true;
 
-    if (roleValue() == IgnoredRole)
+    if (roleValue() == AccessibilityRole::Ignored)
         return true;
     
-    return m_role == UnknownRole;
+    return m_role == AccessibilityRole::Unknown;
 }
 
 bool AccessibilityNodeObject::canvasHasFallbackContent() const
@@ -487,25 +467,26 @@ bool AccessibilityNodeObject::isSearchField() const
     if (!node)
         return false;
 
-    if (roleValue() == SearchFieldRole)
+    if (roleValue() == AccessibilityRole::SearchField)
         return true;
 
-    HTMLInputElement* inputElement = node->toInputElement();
-    if (!inputElement)
+    if (!is<HTMLInputElement>(*node))
         return false;
 
+    auto& inputElement = downcast<HTMLInputElement>(*node);
+
     // Some websites don't label their search fields as such. However, they will
     // use the word "search" in either the form or input type. This won't catch every case,
     // but it will catch google.com for example.
 
     // Check the node name of the input type, sometimes it's "search".
     const AtomicString& nameAttribute = getAttribute(nameAttr);
-    if (nameAttribute.contains("search", false))
+    if (nameAttribute.containsIgnoringASCIICase("search"))
         return true;
 
     // Check the form action and the name, which will sometimes be "search".
-    HTMLFormElement* form = inputElement->form();
-    if (form && (form->name().contains("search", false) || form->action().contains("search", false)))
+    auto* form = inputElement.form();
+    if (form && (form->name().containsIgnoringASCIICase("search") || form->action().containsIgnoringASCIICase("search")))
         return true;
 
     return false;
@@ -533,23 +514,19 @@ bool AccessibilityNodeObject::isNativeImage() const
 
 bool AccessibilityNodeObject::isImage() const
 {
-    return roleValue() == ImageRole;
+    return roleValue() == AccessibilityRole::Image;
 }
 
 bool AccessibilityNodeObject::isPasswordField() const
 {
-    Node* node = this->node();
-    if (!node || !node->isHTMLElement())
-        return false;
-
-    if (ariaRoleAttribute() != UnknownRole)
+    auto* node = this->node();
+    if (!is<HTMLInputElement>(node))
         return false;
 
-    HTMLInputElement* inputElement = node->toInputElement();
-    if (!inputElement)
+    if (ariaRoleAttribute() != AccessibilityRole::Unknown)
         return false;
 
-    return inputElement->isPasswordField();
+    return downcast<HTMLInputElement>(*node).isPasswordField();
 }
 
 AccessibilityObject* AccessibilityNodeObject::passwordFieldOrContainingPasswordField()
@@ -558,16 +535,14 @@ AccessibilityObject* AccessibilityNodeObject::passwordFieldOrContainingPasswordF
     if (!node)
         return nullptr;
 
-    if (HTMLInputElement* inputElement = node->toInputElement()) {
-        if (inputElement->isPasswordField())
-            return this;
-    }
+    if (is<HTMLInputElement>(*node) && downcast<HTMLInputElement>(*node).isPasswordField())
+        return this;
 
-    Element* element = node->shadowHost();
-    if (!element || !is<HTMLInputElement>(element))
+    auto* element = node->shadowHost();
+    if (!is<HTMLInputElement>(element))
         return nullptr;
 
-    if (AXObjectCache* cache = axObjectCache())
+    if (auto* cache = axObjectCache())
         return cache->getOrCreate(element);
 
     return nullptr;
@@ -576,7 +551,7 @@ AccessibilityObject* AccessibilityNodeObject::passwordFieldOrContainingPasswordF
 bool AccessibilityNodeObject::isInputImage() const
 {
     Node* node = this->node();
-    if (is<HTMLInputElement>(node) && roleValue() == ButtonRole) {
+    if (is<HTMLInputElement>(node) && roleValue() == AccessibilityRole::Button) {
         HTMLInputElement& input = downcast<HTMLInputElement>(*node);
         return input.isImageButton();
     }
@@ -586,23 +561,23 @@ bool AccessibilityNodeObject::isInputImage() const
 
 bool AccessibilityNodeObject::isProgressIndicator() const
 {
-    return roleValue() == ProgressIndicatorRole;
+    return roleValue() == AccessibilityRole::ProgressIndicator;
 }
 
 bool AccessibilityNodeObject::isSlider() const
 {
-    return roleValue() == SliderRole;
+    return roleValue() == AccessibilityRole::Slider;
 }
 
 bool AccessibilityNodeObject::isMenuRelated() const
 {
     switch (roleValue()) {
-    case MenuRole:
-    case MenuBarRole:
-    case MenuButtonRole:
-    case MenuItemRole:
-    case MenuItemCheckboxRole:
-    case MenuItemRadioRole:
+    case AccessibilityRole::Menu:
+    case AccessibilityRole::MenuBar:
+    case AccessibilityRole::MenuButton:
+    case AccessibilityRole::MenuItem:
+    case AccessibilityRole::MenuItemCheckbox:
+    case AccessibilityRole::MenuItemRadio:
         return true;
     default:
         return false;
@@ -611,25 +586,25 @@ bool AccessibilityNodeObject::isMenuRelated() const
 
 bool AccessibilityNodeObject::isMenu() const
 {
-    return roleValue() == MenuRole;
+    return roleValue() == AccessibilityRole::Menu;
 }
 
 bool AccessibilityNodeObject::isMenuBar() const
 {
-    return roleValue() == MenuBarRole;
+    return roleValue() == AccessibilityRole::MenuBar;
 }
 
 bool AccessibilityNodeObject::isMenuButton() const
 {
-    return roleValue() == MenuButtonRole;
+    return roleValue() == AccessibilityRole::MenuButton;
 }
 
 bool AccessibilityNodeObject::isMenuItem() const
 {
     switch (roleValue()) {
-    case MenuItemRole:
-    case MenuItemRadioRole:
-    case MenuItemCheckboxRole:
+    case AccessibilityRole::MenuItem:
+    case AccessibilityRole::MenuItemRadio:
+    case AccessibilityRole::MenuItemCheckbox:
         return true;
     default:
         return false;
@@ -639,28 +614,25 @@ bool AccessibilityNodeObject::isMenuItem() const
 bool AccessibilityNodeObject::isNativeCheckboxOrRadio() const
 {
     Node* node = this->node();
-    if (!node)
+    if (!is<HTMLInputElement>(node))
         return false;
 
-    HTMLInputElement* input = node->toInputElement();
-    if (input)
-        return input->isCheckbox() || input->isRadioButton();
-
-    return false;
+    auto& input = downcast<HTMLInputElement>(*node);
+    return input.isCheckbox() || input.isRadioButton();
 }
 
 bool AccessibilityNodeObject::isEnabled() const
 {
     // ARIA says that the disabled status applies to the current element and all descendant elements.
     for (AccessibilityObject* object = const_cast<AccessibilityNodeObject*>(this); object; object = object->parentObject()) {
-        const AtomicString& disabledStatus = object->getAttribute(aria_disabledAttr);
-        if (equalIgnoringCase(disabledStatus, "true"))
-            return false;
-        if (equalIgnoringCase(disabledStatus, "false"))
+        if (auto disabled = object->boolValueForProperty(AXPropertyName::Disabled)) {
+            if (disabled.value())
+                return false;
             break;
+        }
     }
     
-    if (roleValue() == HorizontalRuleRole)
+    if (roleValue() == AccessibilityRole::HorizontalRule)
         return false;
     
     Node* node = this->node();
@@ -672,15 +644,7 @@ bool AccessibilityNodeObject::isEnabled() const
 
 bool AccessibilityNodeObject::isIndeterminate() const
 {
-    Node* node = this->node();
-    if (!node)
-        return false;
-
-    HTMLInputElement* inputElement = node->toInputElement();
-    if (!inputElement)
-        return false;
-
-    return inputElement->shouldAppearIndeterminate();
+    return equalLettersIgnoringASCIICase(getAttribute(indeterminateAttr), "true");
 }
 
 bool AccessibilityNodeObject::isPressed() const
@@ -692,13 +656,9 @@ bool AccessibilityNodeObject::isPressed() const
     if (!node)
         return false;
 
-    // If this is an ARIA button, check the aria-pressed attribute rather than node()->active()
-    AccessibilityRole ariaRole = ariaRoleAttribute();
-    if (ariaRole == ButtonRole || ariaRole == ToggleButtonRole) {
-        if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true"))
-            return true;
-        return false;
-    }
+    // If this is an toggle button, check the aria-pressed attribute rather than node()->active()
+    if (isToggleButton())
+        return equalLettersIgnoringASCIICase(stringValueForProperty(AXPropertyName::Pressed), "true");
 
     if (!is<Element>(*node))
         return false;
@@ -712,26 +672,25 @@ bool AccessibilityNodeObject::isChecked() const
         return false;
 
     // First test for native checkedness semantics
-    HTMLInputElement* inputElement = node->toInputElement();
-    if (inputElement)
-        return inputElement->shouldAppearChecked();
+    if (is<HTMLInputElement>(*node))
+        return downcast<HTMLInputElement>(*node).shouldAppearChecked();
 
     // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute
     bool validRole = false;
     switch (ariaRoleAttribute()) {
-    case RadioButtonRole:
-    case CheckBoxRole:
-    case MenuItemRole:
-    case MenuItemCheckboxRole:
-    case MenuItemRadioRole:
-    case SwitchRole:
+    case AccessibilityRole::RadioButton:
+    case AccessibilityRole::CheckBox:
+    case AccessibilityRole::MenuItem:
+    case AccessibilityRole::MenuItemCheckbox:
+    case AccessibilityRole::MenuItemRadio:
+    case AccessibilityRole::Switch:
         validRole = true;
         break;
     default:
         break;
     }
     
-    if (validRole && equalIgnoringCase(getAttribute(aria_checkedAttr), "true"))
+    if (validRole && equalLettersIgnoringASCIICase(stringValueForProperty(AXPropertyName::Checked), "true"))
         return true;
 
     return false;
@@ -745,41 +704,17 @@ bool AccessibilityNodeObject::isHovered() const
 
 bool AccessibilityNodeObject::isMultiSelectable() const
 {
-    const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr);
-    if (equalIgnoringCase(ariaMultiSelectable, "true"))
-        return true;
-    if (equalIgnoringCase(ariaMultiSelectable, "false"))
-        return false;
+    if (auto multiSelectable = boolValueForProperty(AXPropertyName::Multiselectable))
+        return multiSelectable.value();
     
     return node() && node()->hasTagName(selectTag) && downcast<HTMLSelectElement>(*node()).multiple();
 }
 
-bool AccessibilityNodeObject::isReadOnly() const
-{
-    Node* node = this->node();
-    if (!node)
-        return true;
-
-    if (is<HTMLTextAreaElement>(*node))
-        return downcast<HTMLTextAreaElement>(*node).isReadOnly();
-
-    if (is<HTMLInputElement>(*node)) {
-        HTMLInputElement& input = downcast<HTMLInputElement>(*node);
-        if (input.isTextField())
-            return input.isReadOnly();
-    }
-
-    return !node->hasEditableStyle();
-}
-
 bool AccessibilityNodeObject::isRequired() const
 {
     // Explicit aria-required values should trump native required attributes.
-    const AtomicString& requiredValue = getAttribute(aria_requiredAttr);
-    if (equalIgnoringCase(requiredValue, "true"))
-        return true;
-    if (equalIgnoringCase(requiredValue, "false"))
-        return false;
+    if (auto axRequired = boolValueForProperty(AXPropertyName::Required))
+        return axRequired.value();
 
     Node* n = this->node();
     if (is<HTMLFormControlElement>(n))
@@ -791,25 +726,26 @@ bool AccessibilityNodeObject::isRequired() const
 bool AccessibilityNodeObject::supportsRequiredAttribute() const
 {
     switch (roleValue()) {
-    case ButtonRole:
+    case AccessibilityRole::Button:
         return isFileUploadButton();
-    case CellRole:
-    case ColumnHeaderRole:
-    case CheckBoxRole:
-    case ComboBoxRole:
-    case GridRole:
-    case IncrementorRole:
-    case ListBoxRole:
-    case PopUpButtonRole:
-    case RadioButtonRole:
-    case RadioGroupRole:
-    case RowHeaderRole:
-    case SliderRole:
-    case SpinButtonRole:
-    case TableHeaderContainerRole:
-    case TextAreaRole:
-    case TextFieldRole:
-    case ToggleButtonRole:
+    case AccessibilityRole::Cell:
+    case AccessibilityRole::ColumnHeader:
+    case AccessibilityRole::CheckBox:
+    case AccessibilityRole::ComboBox:
+    case AccessibilityRole::Grid:
+    case AccessibilityRole::GridCell:
+    case AccessibilityRole::Incrementor:
+    case AccessibilityRole::ListBox:
+    case AccessibilityRole::PopUpButton:
+    case AccessibilityRole::RadioButton:
+    case AccessibilityRole::RadioGroup:
+    case AccessibilityRole::RowHeader:
+    case AccessibilityRole::Slider:
+    case AccessibilityRole::SpinButton:
+    case AccessibilityRole::TableHeaderContainer:
+    case AccessibilityRole::TextArea:
+    case AccessibilityRole::TextField:
+    case AccessibilityRole::ToggleButton:
         return true;
     default:
         return false;
@@ -824,9 +760,9 @@ int AccessibilityNodeObject::headingLevel() const
         return false;
 
     if (isHeading()) {
-        int ariaLevel = getAttribute(aria_levelAttr).toInt();
-        if (ariaLevel > 0)
-            return ariaLevel;
+        int level = unsignedValueForProperty(AXPropertyName::Level);
+        if (level > 0)
+            return level;
     }
 
     if (node->hasTagName(h1Tag))
@@ -847,6 +783,11 @@ int AccessibilityNodeObject::headingLevel() const
     if (node->hasTagName(h6Tag))
         return 6;
 
+    // The implicit value of aria-level is 2 for the heading role.
+    // https://www.w3.org/TR/wai-aria-1.1/#heading
+    if (ariaRoleAttribute() == AccessibilityRole::Heading)
+        return 2;
+
     return 0;
 }
 
@@ -855,7 +796,7 @@ String AccessibilityNodeObject::valueDescription() const
     if (!isRangeControl())
         return String();
 
-    return getAttribute(aria_valuetextAttr).string();
+    return stringValueForProperty(AXPropertyName::ValueText);
 }
 
 float AccessibilityNodeObject::valueForRange() const
@@ -869,7 +810,12 @@ float AccessibilityNodeObject::valueForRange() const
     if (!isRangeControl())
         return 0.0f;
 
-    return getAttribute(aria_valuenowAttr).toFloat();
+    // In ARIA 1.1, the implicit value for aria-valuenow on a spin button is 0.
+    // For other roles, it is half way between aria-valuemin and aria-valuemax.
+    if (hasProperty(AXPropertyName::ValueNow))
+        return doubleValueForProperty(AXPropertyName::ValueNow);
+
+    return isSpinButton() ? 0 : (minValueForRange() + maxValueForRange()) / 2;
 }
 
 float AccessibilityNodeObject::maxValueForRange() const
@@ -883,7 +829,12 @@ float AccessibilityNodeObject::maxValueForRange() const
     if (!isRangeControl())
         return 0.0f;
 
-    return getAttribute(aria_valuemaxAttr).toFloat();
+    if (hasProperty(AXPropertyName::ValueMax))
+        return doubleValueForProperty(AXPropertyName::ValueMax);
+
+    // In ARIA 1.1, the implicit value for aria-valuemax on a spin button
+    // is that there is no maximum value. For other roles, it is 100.
+    return isSpinButton() ? std::numeric_limits<float>::max() : 100.0f;
 }
 
 float AccessibilityNodeObject::minValueForRange() const
@@ -897,7 +848,12 @@ float AccessibilityNodeObject::minValueForRange() const
     if (!isRangeControl())
         return 0.0f;
 
-    return getAttribute(aria_valueminAttr).toFloat();
+    if (hasProperty(AXPropertyName::ValueMin))
+        return doubleValueForProperty(AXPropertyName::ValueMin);
+
+    // In ARIA 1.1, the implicit value for aria-valuemin on a spin button
+    // is that there is no minimum value. For other roles, it is 0.
+    return isSpinButton() ? -std::numeric_limits<float>::max() : 0.0f;
 }
 
 float AccessibilityNodeObject::stepValueForRange() const
@@ -907,12 +863,12 @@ float AccessibilityNodeObject::stepValueForRange() const
 
 bool AccessibilityNodeObject::isHeading() const
 {
-    return roleValue() == HeadingRole;
+    return roleValue() == AccessibilityRole::Heading;
 }
 
 bool AccessibilityNodeObject::isLink() const
 {
-    return roleValue() == WebCoreLinkRole;
+    return roleValue() == AccessibilityRole::WebCoreLink;
 }
 
 bool AccessibilityNodeObject::isControl() const
@@ -935,7 +891,8 @@ bool AccessibilityNodeObject::isFieldset() const
 
 bool AccessibilityNodeObject::isGroup() const
 {
-    return roleValue() == GroupRole;
+    AccessibilityRole role = roleValue();
+    return role == AccessibilityRole::Group || role == AccessibilityRole::TextGroup || role == AccessibilityRole::ApplicationGroup || role == AccessibilityRole::ApplicationTextGroup;
 }
 
 AccessibilityObject* AccessibilityNodeObject::selectedRadioButton()
@@ -945,7 +902,7 @@ AccessibilityObject* AccessibilityNodeObject::selectedRadioButton()
 
     // Find the child radio button that is selected (ie. the intValue == 1).
     for (const auto& child : children()) {
-        if (child->roleValue() == RadioButtonRole && child->checkboxOrRadioValue() == ButtonStateOn)
+        if (child->roleValue() == AccessibilityRole::RadioButton && child->checkboxOrRadioValue() == AccessibilityButtonState::On)
             return child.get();
     }
     return nullptr;
@@ -956,12 +913,13 @@ AccessibilityObject* AccessibilityNodeObject::selectedTabItem()
     if (!isTabList())
         return nullptr;
 
+    // FIXME: Is this valid? ARIA tab items support aria-selected; not aria-checked.
     // Find the child tab item that is selected (ie. the intValue == 1).
     AccessibilityObject::AccessibilityChildrenVector tabs;
     tabChildren(tabs);
 
     for (const auto& child : children()) {
-        if (child->isTabItem() && child->isChecked())
+        if (child->isTabItem() && (child->isChecked() || child->isSelected()))
             return child.get();
     }
     return nullptr;
@@ -970,7 +928,7 @@ AccessibilityObject* AccessibilityNodeObject::selectedTabItem()
 AccessibilityButtonState AccessibilityNodeObject::checkboxOrRadioValue() const
 {
     if (isNativeCheckboxOrRadio())
-        return isChecked() ? ButtonStateOn : ButtonStateOff;
+        return isIndeterminate() ? AccessibilityButtonState::Mixed : isChecked() ? AccessibilityButtonState::On : AccessibilityButtonState::Off;
 
     return AccessibilityObject::checkboxOrRadioValue();
 }
@@ -1037,14 +995,14 @@ Element* AccessibilityNodeObject::actionElement() const
         return downcast<Element>(node);
 
     switch (roleValue()) {
-    case ButtonRole:
-    case PopUpButtonRole:
-    case ToggleButtonRole:
-    case TabRole:
-    case MenuItemRole:
-    case MenuItemCheckboxRole:
-    case MenuItemRadioRole:
-    case ListItemRole:
+    case AccessibilityRole::Button:
+    case AccessibilityRole::PopUpButton:
+    case AccessibilityRole::ToggleButton:
+    case AccessibilityRole::Tab:
+    case AccessibilityRole::MenuItem:
+    case AccessibilityRole::MenuItemCheckbox:
+    case AccessibilityRole::MenuItemRadio:
+    case AccessibilityRole::ListItem:
         // Check if the author is hiding the real control element inside the ARIA element.
         if (Element* nativeElement = nativeActionElement(node))
             return nativeElement;
@@ -1082,6 +1040,9 @@ Element* AccessibilityNodeObject::mouseButtonListener(MouseButtonListenerResultF
 
 bool AccessibilityNodeObject::isDescendantOfBarrenParent() const
 {
+    if (!m_isIgnoredFromParentData.isNull())
+        return m_isIgnoredFromParentData.isDescendantOfBarrenParent;
+    
     for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) {
         if (!object->canHaveChildren())
             return true;
@@ -1092,7 +1053,7 @@ bool AccessibilityNodeObject::isDescendantOfBarrenParent() const
 
 void AccessibilityNodeObject::alterSliderValue(bool increase)
 {
-    if (roleValue() != SliderRole)
+    if (roleValue() != AccessibilityRole::Slider)
         return;
 
     if (!getAttribute(stepAttr).isEmpty())
@@ -1103,13 +1064,13 @@ void AccessibilityNodeObject::alterSliderValue(bool increase)
     
 void AccessibilityNodeObject::increment()
 {
-    UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture, document());
+    UserGestureIndicator gestureIndicator(ProcessingUserGesture, document());
     alterSliderValue(true);
 }
 
 void AccessibilityNodeObject::decrement()
 {
-    UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture, document());
+    UserGestureIndicator gestureIndicator(ProcessingUserGesture, document());
     alterSliderValue(false);
 }
 
@@ -1151,11 +1112,11 @@ bool AccessibilityNodeObject::isGenericFocusableElement() const
         return false;
     
     AccessibilityRole role = roleValue();
-    if (role == VideoRole || role == AudioRole)
+    if (role == AccessibilityRole::Video || role == AccessibilityRole::Audio)
         return false;
 
     // If it has an aria role, it's not generic.
-    if (m_ariaRole != UnknownRole)
+    if (m_ariaRole != AccessibilityRole::Unknown)
         return false;
 
     // If the content editable attribute is set on this element, that's the reason
@@ -1167,14 +1128,14 @@ bool AccessibilityNodeObject::isGenericFocusableElement() const
 
     // The web area and body element are both focusable, but existing logic handles these
     // cases already, so we don't need to include them here.
-    if (role == WebAreaRole)
+    if (role == AccessibilityRole::WebArea)
         return false;
     if (node() && node()->hasTagName(bodyTag))
         return false;
 
     // An SVG root is focusable by default, but it's probably not interactive, so don't
     // include it. It can still be made accessible by giving it an ARIA role.
-    if (role == SVGRootRole)
+    if (role == AccessibilityRole::SVGRoot)
         return false;
 
     return true;
@@ -1200,22 +1161,23 @@ String AccessibilityNodeObject::ariaAccessibilityDescription() const
     if (!ariaLabeledBy.isEmpty())
         return ariaLabeledBy;
 
-    const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
+    const AtomicString& ariaLabel = stringValueForProperty(AXPropertyName::Label);
     if (!ariaLabel.isEmpty())
         return ariaLabel;
 
     return String();
 }
 
-static Element* siblingWithAriaRole(String role, Node* node)
+static Element* siblingWithAriaRole(Node* node, const char* role)
 {
+    // FIXME: Either we should add a null check here or change the function to take a reference instead of a pointer.
     ContainerNode* parent = node->parentNode();
     if (!parent)
         return nullptr;
 
     for (auto& sibling : childrenOfType<Element>(*parent)) {
-        const AtomicString& siblingAriaRole = sibling.fastGetAttribute(roleAttr);
-        if (equalIgnoringCase(siblingAriaRole, role))
+        // FIXME: Should skip sibling that is the same as the node.
+        if (equalIgnoringASCIICase(AccessibleNode::effectiveStringValueForElement(sibling, AXPropertyName::Role), role))
             return &sibling;
     }
 
@@ -1224,10 +1186,10 @@ static Element* siblingWithAriaRole(String role, Node* node)
 
 Element* AccessibilityNodeObject::menuElementForMenuButton() const
 {
-    if (ariaRoleAttribute() != MenuButtonRole)
+    if (ariaRoleAttribute() != AccessibilityRole::MenuButton)
         return nullptr;
 
-    return siblingWithAriaRole("menu", node());
+    return siblingWithAriaRole(node(), "menu");
 }
 
 AccessibilityObject* AccessibilityNodeObject::menuForMenuButton() const
@@ -1239,10 +1201,10 @@ AccessibilityObject* AccessibilityNodeObject::menuForMenuButton() const
 
 Element* AccessibilityNodeObject::menuItemElementForMenu() const
 {
-    if (ariaRoleAttribute() != MenuRole)
+    if (ariaRoleAttribute() != AccessibilityRole::Menu)
         return nullptr;
     
-    return siblingWithAriaRole("menuitem", node());    
+    return siblingWithAriaRole(node(), "menuitem");
 }
 
 AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const
@@ -1262,10 +1224,54 @@ AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const
     return nullptr;
 }
 
+AccessibilityObject* AccessibilityNodeObject::captionForFigure() const
+{
+    if (!isFigureElement())
+        return nullptr;
+    
+    AXObjectCache* cache = axObjectCache();
+    if (!cache)
+        return nullptr;
+    
+    Node* node = this->node();
+    for (Node* child = node->firstChild(); child; child = child->nextSibling()) {
+        if (child->hasTagName(figcaptionTag))
+            return cache->getOrCreate(child);
+    }
+    return nullptr;
+}
+
 bool AccessibilityNodeObject::usesAltTagForTextComputation() const
 {
     return isImage() || isInputImage() || isNativeImage() || isCanvas() || (node() && node()->hasTagName(imgTag));
 }
+
+bool AccessibilityNodeObject::isLabelable() const
+{
+    Node* node = this->node();
+    if (!node)
+        return false;
+    
+    return is<HTMLInputElement>(*node) || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl() || isProgressIndicator() || isMeter();
+}
+
+String AccessibilityNodeObject::textForLabelElement(Element* element) const
+{
+    String result = String();
+    if (!is<HTMLLabelElement>(*element))
+        return result;
+    
+    HTMLLabelElement* label = downcast<HTMLLabelElement>(element);
+    // Check to see if there's aria-labelledby attribute on the label element.
+    if (AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label))
+        result = labelObject->ariaLabeledByAttribute();
+    
+    // Then check for aria-label attribute.
+    if (result.isEmpty())
+        result = AccessibleNode::effectiveStringValueForElement(*label, AXPropertyName::Label);
+    
+    return !result.isEmpty() ? result : label->innerText();
+}
     
 void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOrder) const
 {
@@ -1273,21 +1279,21 @@ void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOr
     if (!node)
         return;
     
-    bool isInputTag = is<HTMLInputElement>(*node);
-    if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
+    if (isLabelable()) {
         if (HTMLLabelElement* label = labelForElement(downcast<Element>(node))) {
             AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label);
-            String innerText = label->innerText();
+            String innerText = textForLabelElement(label);
+            
             // Only use the <label> text if there's no ARIA override.
             if (!innerText.isEmpty() && !ariaAccessibilityDescription())
-                textOrder.append(AccessibilityText(innerText, LabelByElementText, labelObject));
+                textOrder.append(AccessibilityText(innerText, isMeter() ? AccessibilityTextSource::Alternative : AccessibilityTextSource::LabelByElement, labelObject));
             return;
         }
     }
     
     AccessibilityObject* titleUIElement = this->titleUIElement();
     if (titleUIElement)
-        textOrder.append(AccessibilityText(String(), LabelByElementText, titleUIElement));
+        textOrder.append(AccessibilityText(String(), AccessibilityTextSource::LabelByElement, titleUIElement));
 }
 
 void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrder) const
@@ -1295,15 +1301,15 @@ void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrd
     if (isWebArea()) {
         String webAreaText = alternativeTextForWebArea();
         if (!webAreaText.isEmpty())
-            textOrder.append(AccessibilityText(webAreaText, AlternativeText));
+            textOrder.append(AccessibilityText(webAreaText, AccessibilityTextSource::Alternative));
         return;
     }
     
     ariaLabeledByText(textOrder);
     
-    const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
+    const AtomicString& ariaLabel = stringValueForProperty(AXPropertyName::Label);
     if (!ariaLabel.isEmpty())
-        textOrder.append(AccessibilityText(ariaLabel, AlternativeText));
+        textOrder.append(AccessibilityText(ariaLabel, AccessibilityTextSource::Alternative));
     
     if (usesAltTagForTextComputation()) {
         if (is<RenderImage>(renderer())) {
@@ -1311,7 +1317,7 @@ void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrd
 
             // RenderImage will return title as a fallback from altText, but we don't want title here because we consider that in helpText.
             if (!renderAltText.isEmpty() && renderAltText != getAttribute(titleAttr)) {
-                textOrder.append(AccessibilityText(renderAltText, AlternativeText));
+                textOrder.append(AccessibilityText(renderAltText, AccessibilityTextSource::Alternative));
                 return;
             }
         }
@@ -1319,7 +1325,7 @@ void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrd
         // Otherwise, it should fallback to other methods, like the title attribute.
         const AtomicString& alt = getAttribute(altAttr);
         if (!alt.isEmpty())
-            textOrder.append(AccessibilityText(alt, AlternativeText));
+            textOrder.append(AccessibilityText(alt, AccessibilityTextSource::Alternative));
     }
     
     Node* node = this->node();
@@ -1330,16 +1336,23 @@ void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrd
     if (is<HTMLFieldSetElement>(*node)) {
         AccessibilityObject* object = axObjectCache()->getOrCreate(downcast<HTMLFieldSetElement>(*node).legend());
         if (object && !object->isHidden())
-            textOrder.append(AccessibilityText(accessibleNameForNode(object->node()), AlternativeText));
+            textOrder.append(AccessibilityText(accessibleNameForNode(object->node()), AccessibilityTextSource::Alternative));
+    }
+    
+    // The figure element derives its alternative text from the first associated figcaption element if one is available.
+    if (isFigureElement()) {
+        AccessibilityObject* captionForFigure = this->captionForFigure();
+        if (captionForFigure && !captionForFigure->isHidden())
+            textOrder.append(AccessibilityText(accessibleNameForNode(captionForFigure->node()), AccessibilityTextSource::Alternative));
     }
     
-    // SVG elements all can have a <svg:title> element inside which should act as the descriptive text.
-    if (node->isSVGElement())
-        textOrder.append(AccessibilityText(downcast<SVGElement>(*node).title(), AlternativeText));
+    // Tree items missing a label are labeled by all child elements.
+    if (isTreeItem() && ariaLabel.isEmpty() && ariaLabeledByAttribute().isEmpty())
+        textOrder.append(AccessibilityText(accessibleNameForNode(node), AccessibilityTextSource::Alternative));
     
 #if ENABLE(MATHML)
     if (node->isMathMLElement())
-        textOrder.append(AccessibilityText(getAttribute(MathMLNames::alttextAttr), AlternativeText));
+        textOrder.append(AccessibilityText(getAttribute(MathMLNames::alttextAttr), AccessibilityTextSource::Alternative));
 #endif
 }
 
@@ -1353,7 +1366,7 @@ void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder)
     if (isInputTag) {
         HTMLInputElement& input = downcast<HTMLInputElement>(*node);
         if (input.isTextButton()) {
-            textOrder.append(AccessibilityText(input.valueWithDefault(), VisibleText));
+            textOrder.append(AccessibilityText(input.valueWithDefault(), AccessibilityTextSource::Visible));
             return;
         }
     }
@@ -1365,27 +1378,26 @@ void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder)
     bool useTextUnderElement = false;
     
     switch (roleValue()) {
-    case PopUpButtonRole:
+    case AccessibilityRole::PopUpButton:
         // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
         if (node->hasTagName(selectTag))
             break;
         FALLTHROUGH;
-    case ButtonRole:
-    case ToggleButtonRole:
-    case CheckBoxRole:
-    case ListBoxOptionRole:
+    case AccessibilityRole::Button:
+    case AccessibilityRole::ToggleButton:
+    case AccessibilityRole::CheckBox:
+    case AccessibilityRole::ListBoxOption:
     // MacOS does not expect native <li> elements to expose label information, it only expects leaf node elements to do that.
 #if !PLATFORM(COCOA)
-    case ListItemRole:
+    case AccessibilityRole::ListItem:
 #endif
-    case MenuButtonRole:
-    case MenuItemRole:
-    case MenuItemCheckboxRole:
-    case MenuItemRadioRole:
-    case RadioButtonRole:
-    case SwitchRole:
-    case TabRole:
-    case ProgressIndicatorRole:
+    case AccessibilityRole::MenuButton:
+    case AccessibilityRole::MenuItem:
+    case AccessibilityRole::MenuItemCheckbox:
+    case AccessibilityRole::MenuItemRadio:
+    case AccessibilityRole::RadioButton:
+    case AccessibilityRole::Switch:
+    case AccessibilityRole::Tab:
         useTextUnderElement = true;
         break;
     default:
@@ -1397,6 +1409,9 @@ void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder)
     if (isHeading() || isLink())
         useTextUnderElement = true;
     
+    if (isOutput())
+        useTextUnderElement = true;
+    
     if (useTextUnderElement) {
         AccessibilityTextUnderElementMode mode;
         
@@ -1406,7 +1421,7 @@ void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder)
 
         String text = textUnderElement(mode);
         if (!text.isEmpty())
-            textOrder.append(AccessibilityText(text, ChildrenText));
+            textOrder.append(AccessibilityText(text, AccessibilityTextSource::Children));
     }
 }
 
@@ -1414,21 +1429,29 @@ void AccessibilityNodeObject::helpText(Vector<AccessibilityText>& textOrder) con
 {
     const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
     if (!ariaHelp.isEmpty())
-        textOrder.append(AccessibilityText(ariaHelp, HelpText));
+        textOrder.append(AccessibilityText(ariaHelp, AccessibilityTextSource::Help));
     
     String describedBy = ariaDescribedByAttribute();
     if (!describedBy.isEmpty())
-        textOrder.append(AccessibilityText(describedBy, SummaryText));
+        textOrder.append(AccessibilityText(describedBy, AccessibilityTextSource::Summary));
 
     // Summary attribute used as help text on tables.
     const AtomicString& summary = getAttribute(summaryAttr);
     if (!summary.isEmpty())
-        textOrder.append(AccessibilityText(summary, SummaryText));
+        textOrder.append(AccessibilityText(summary, AccessibilityTextSource::Summary));
 
     // The title attribute should be used as help text unless it is already being used as descriptive text.
+    // However, when the title attribute is the only text alternative provided, it may be exposed as the
+    // descriptive text. This is problematic in the case of meters because the HTML spec suggests authors
+    // can expose units through this attribute. Therefore, if the element is a meter, change its source
+    // type to AccessibilityTextSource::Help.
     const AtomicString& title = getAttribute(titleAttr);
-    if (!title.isEmpty())
-        textOrder.append(AccessibilityText(title, TitleTagText));
+    if (!title.isEmpty()) {
+        if (!isMeter() && !roleIgnoresTitle())
+            textOrder.append(AccessibilityText(title, AccessibilityTextSource::TitleTag));
+        else
+            textOrder.append(AccessibilityText(title, AccessibilityTextSource::Help));
+    }
 }
 
 void AccessibilityNodeObject::accessibilityText(Vector<AccessibilityText>& textOrder)
@@ -1440,7 +1463,7 @@ void AccessibilityNodeObject::accessibilityText(Vector<AccessibilityText>& textO
     
     String placeholder = placeholderValue();
     if (!placeholder.isEmpty())
-        textOrder.append(AccessibilityText(placeholder, PlaceholderText));
+        textOrder.append(AccessibilityText(placeholder, AccessibilityTextSource::Placeholder));
 }
     
 void AccessibilityNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textOrder) const
@@ -1451,12 +1474,10 @@ void AccessibilityNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textO
         ariaLabeledByElements(elements);
         
         Vector<RefPtr<AccessibilityObject>> axElements;
-        for (const auto& element : elements) {
-            RefPtr<AccessibilityObject> axElement = axObjectCache()->getOrCreate(element);
-            axElements.append(axElement);
-        }
+        for (const auto& element : elements)
+            axElements.append(axObjectCache()->getOrCreate(element));
         
-        textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, WTF::move(axElements)));
+        textOrder.append(AccessibilityText(ariaLabeledBy, AccessibilityTextSource::Alternative, WTFMove(axElements)));
     }
 }
     
@@ -1478,14 +1499,14 @@ String AccessibilityNodeObject::alternativeTextForWebArea() const
     
     // Check if the HTML element has an aria-label for the webpage.
     if (Element* documentElement = document->documentElement()) {
-        const AtomicString& ariaLabel = documentElement->fastGetAttribute(aria_labelAttr);
+        const AtomicString& ariaLabel = AccessibleNode::effectiveStringValueForElement(*documentElement, AXPropertyName::Label);
         if (!ariaLabel.isEmpty())
             return ariaLabel;
     }
     
     if (auto* owner = document->ownerElement()) {
         if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) {
-            const AtomicString& title = owner->fastGetAttribute(titleAttr);
+            const AtomicString& title = owner->attributeWithoutSynchronization(titleAttr);
             if (!title.isEmpty())
                 return title;
         }
@@ -1505,7 +1526,7 @@ String AccessibilityNodeObject::alternativeTextForWebArea() const
 String AccessibilityNodeObject::accessibilityDescription() const
 {
     // Static text should not have a description, it should only have a stringValue.
-    if (roleValue() == StaticTextRole)
+    if (roleValue() == AccessibilityRole::StaticText)
         return String();
 
     String ariaDescription = ariaAccessibilityDescription();
@@ -1519,10 +1540,6 @@ String AccessibilityNodeObject::accessibilityDescription() const
         if (!alt.isNull())
             return alt;
     }
-
-    // SVG elements all can have a <svg:title> element inside which should act as the descriptive text.
-    if (m_node && m_node->isSVGElement())
-        return downcast<SVGElement>(*m_node).title();
     
 #if ENABLE(MATHML)
     if (is<MathMLElement>(m_node))
@@ -1533,12 +1550,29 @@ String AccessibilityNodeObject::accessibilityDescription() const
     // Both are used to generate what a screen reader speaks.                                                           
     // If this point is reached (i.e. there's no accessibilityDescription) and there's no title(), we should fallback to using the title attribute.
     // The title attribute is normally used as help text (because it is a tooltip), but if there is nothing else available, this should be used (according to ARIA).
-    if (title().isEmpty())
+    // https://bugs.webkit.org/show_bug.cgi?id=170475: An exception is when the element is semantically unimportant. In those cases, title text should remain as help text.
+    if (title().isEmpty() && !roleIgnoresTitle())
         return getAttribute(titleAttr);
 
     return String();
 }
 
+// Returns whether the role was not intended to play a semantically meaningful part of the
+// accessibility hierarchy. This applies to generic groups like <div>'s with no role value set.
+bool AccessibilityNodeObject::roleIgnoresTitle() const
+{
+    if (ariaRoleAttribute() != AccessibilityRole::Unknown)
+        return false;
+
+    switch (roleValue()) {
+    case AccessibilityRole::Div:
+    case AccessibilityRole::Unknown:
+        return true;
+    default:
+        return false;
+    }
+}
+
 String AccessibilityNodeObject::helpText() const
 {
     Node* node = this->node();
@@ -1569,10 +1603,8 @@ String AccessibilityNodeObject::helpText() const
         
         // Only take help text from an ancestor element if its a group or an unknown role. If help was 
         // added to those kinds of elements, it is likely it was meant for a child element.
-        AccessibilityObject* axObj = axObjectCache()->getOrCreate(ancestor);
-        if (axObj) {
-            AccessibilityRole role = axObj->roleValue();
-            if (role != GroupRole && role != UnknownRole)
+        if (AccessibilityObject* axObj = axObjectCache()->getOrCreate(ancestor)) {
+            if (!axObj->isGroup() && axObj->roleValue() != AccessibilityRole::Unknown)
                 break;
         }
     }
@@ -1585,29 +1617,36 @@ unsigned AccessibilityNodeObject::hierarchicalLevel() const
     Node* node = this->node();
     if (!is<Element>(node))
         return 0;
-    Element& element = downcast<Element>(*node);
-    const AtomicString& ariaLevel = element.fastGetAttribute(aria_levelAttr);
-    if (!ariaLevel.isEmpty())
-        return ariaLevel.toInt();
+    if (hasProperty(AXPropertyName::Level))
+        return unsignedValueForProperty(AXPropertyName::Level);
     
     // Only tree item will calculate its level through the DOM currently.
-    if (roleValue() != TreeItemRole)
+    if (roleValue() != AccessibilityRole::TreeItem)
         return 0;
     
     // Hierarchy leveling starts at 1, to match the aria-level spec.
     // We measure tree hierarchy by the number of groups that the item is within.
     unsigned level = 1;
     for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
-        AccessibilityRole parentRole = parent->roleValue();
-        if (parentRole == GroupRole)
+        AccessibilityRole parentRole = parent->ariaRoleAttribute();
+        if (parentRole == AccessibilityRole::ApplicationGroup)
             level++;
-        else if (parentRole == TreeRole)
+        else if (parentRole == AccessibilityRole::Tree)
             break;
     }
     
     return level;
 }
 
+void AccessibilityNodeObject::setIsExpanded(bool expand)
+{
+    if (is<HTMLDetailsElement>(node())) {
+        auto& details = downcast<HTMLDetailsElement>(*node());
+        if (expand != details.isOpen())
+            details.toggleOpen();
+    }
+}
+    
 // When building the textUnderElement for an object, determine whether or not
 // we should include the inner text of this given descendant object or skip it.
 static bool shouldUseAccessibilityObjectInnerText(AccessibilityObject* obj, AccessibilityTextUnderElementMode mode)
@@ -1638,7 +1677,7 @@ static bool shouldUseAccessibilityObjectInnerText(AccessibilityObject* obj, Acce
         && !obj->accessibleNameDerivesFromContent())
         return false;
     
-    if (equalIgnoringCase(obj->getAttribute(aria_hiddenAttr), "true"))
+    if (obj->boolValueForProperty(AXPropertyName::Hidden).value())
         return false;
     
     // If something doesn't expose any children, then we can always take the inner text content.
@@ -1685,12 +1724,6 @@ String AccessibilityNodeObject::textUnderElement(AccessibilityTextUnderElementMo
     if (is<Text>(node))
         return downcast<Text>(*node).wholeText();
 
-    // The render tree should be stable before going ahead. Otherwise, further uses of the
-    // TextIterator will force a layout update, potentially altering the accessibility tree
-    // and leading to crashes in the loop that computes the result text from the children.
-    ASSERT(!document()->renderView()->layoutState());
-    ASSERT(!document()->childNeedsStyleRecalc());
-
     StringBuilder builder;
     for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) {
         if (mode.ignoredChildNode && child->node() == mode.ignoredChildNode)
@@ -1706,6 +1739,14 @@ String AccessibilityNodeObject::textUnderElement(AccessibilityTextUnderElementMo
             continue;
 
         if (is<AccessibilityNodeObject>(*child)) {
+            // We should ignore the child if it's labeled by this node.
+            // This could happen when this node labels multiple child nodes and we didn't
+            // skip in the above ignoredChildNode check.
+            Vector<Element*> labeledByElements;
+            downcast<AccessibilityNodeObject>(*child).ariaLabeledByElements(labeledByElements);
+            if (labeledByElements.contains(node))
+                continue;
+            
             Vector<AccessibilityText> textOrder;
             downcast<AccessibilityNodeObject>(*child).alternativeText(textOrder);
             if (textOrder.size() > 0 && textOrder[0].text.length()) {
@@ -1735,11 +1776,11 @@ String AccessibilityNodeObject::title() const
             return input.valueWithDefault();
     }
 
-    if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
+    if (isLabelable()) {
         HTMLLabelElement* label = labelForElement(downcast<Element>(node));
         // Use the label text as the title if 1) the title element is NOT an exposed element and 2) there's no ARIA override.
         if (label && !exposesTitleUIElement() && !ariaAccessibilityDescription().length())
-            return label->innerText();
+            return textForLabelElement(label);
     }
 
     // If this node isn't rendered, there's no inner text we can extract from a select element.
@@ -1747,26 +1788,26 @@ String AccessibilityNodeObject::title() const
         return String();
 
     switch (roleValue()) {
-    case PopUpButtonRole:
+    case AccessibilityRole::PopUpButton:
         // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
         if (node->hasTagName(selectTag))
             return String();
         FALLTHROUGH;
-    case ButtonRole:
-    case ToggleButtonRole:
-    case CheckBoxRole:
-    case ListBoxOptionRole:
-    case ListItemRole:
-    case MenuButtonRole:
-    case MenuItemRole:
-    case MenuItemCheckboxRole:
-    case MenuItemRadioRole:
-    case RadioButtonRole:
-    case SwitchRole:
-    case TabRole:
+    case AccessibilityRole::Button:
+    case AccessibilityRole::ToggleButton:
+    case AccessibilityRole::CheckBox:
+    case AccessibilityRole::ListBoxOption:
+    case AccessibilityRole::ListItem:
+    case AccessibilityRole::MenuButton:
+    case AccessibilityRole::MenuItem:
+    case AccessibilityRole::MenuItemCheckbox:
+    case AccessibilityRole::MenuItemRadio:
+    case AccessibilityRole::RadioButton:
+    case AccessibilityRole::Switch:
+    case AccessibilityRole::Tab:
         return textUnderElement();
     // SVGRoots should not use the text under itself as a title. That could include the text of objects like <text>.
-    case SVGRootRole:
+    case AccessibilityRole::SVGRoot:
         return String();
     default:
         break;
@@ -1783,7 +1824,7 @@ String AccessibilityNodeObject::title() const
 String AccessibilityNodeObject::text() const
 {
     // If this is a user defined static text, use the accessible name computation.                                      
-    if (ariaRoleAttribute() == StaticTextRole) {
+    if (isARIAStaticText()) {
         Vector<AccessibilityText> textOrder;
         alternativeText(textOrder);
         if (textOrder.size() > 0 && textOrder[0].text.length())
@@ -1812,7 +1853,7 @@ String AccessibilityNodeObject::stringValue() const
     if (!node)
         return String();
 
-    if (ariaRoleAttribute() == StaticTextRole) {
+    if (isARIAStaticText()) {
         String staticText = text();
         if (!staticText.length())
             staticText = textUnderElement();
@@ -1827,7 +1868,7 @@ String AccessibilityNodeObject::stringValue() const
         int selectedIndex = selectElement.selectedIndex();
         const Vector<HTMLElement*>& listItems = selectElement.listItems();
         if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) {
-            const AtomicString& overriddenDescription = listItems[selectedIndex]->fastGetAttribute(aria_labelAttr);
+            const AtomicString& overriddenDescription = AccessibleNode::effectiveStringValueForElement(*listItems[selectedIndex], AXPropertyName::Label);
             if (!overriddenDescription.isNull())
                 return overriddenDescription;
         }
@@ -1852,22 +1893,18 @@ void AccessibilityNodeObject::colorValue(int& r, int& g, int& b) const
     g = 0;
     b = 0;
 
+#if ENABLE(INPUT_TYPE_COLOR)
     if (!isColorWell())
         return;
 
     if (!is<HTMLInputElement>(node()))
         return;
 
-    HTMLInputElement& input = downcast<HTMLInputElement>(*node());
-    const AtomicString& type = input.getAttribute(typeAttr);
-    if (!equalIgnoringCase(type, "color"))
-        return;
-
-    // HTMLInputElement::value always returns a string parseable by Color().
-    Color color(input.value());
+    auto color = downcast<HTMLInputElement>(*node()).valueAsColor();
     r = color.red();
     g = color.green();
     b = color.blue();
+#endif
 }
 
 // This function implements the ARIA accessible name as described by the Mozilla                                        
@@ -1879,11 +1916,11 @@ static String accessibleNameForNode(Node* node, Node* labelledbyNode)
         return String();
     
     Element& element = downcast<Element>(*node);
-    const AtomicString& ariaLabel = element.fastGetAttribute(aria_labelAttr);
+    const AtomicString& ariaLabel = AccessibleNode::effectiveStringValueForElement(element, AXPropertyName::Label);
     if (!ariaLabel.isEmpty())
         return ariaLabel;
     
-    const AtomicString& alt = element.fastGetAttribute(altAttr);
+    const AtomicString& alt = element.attributeWithoutSynchronization(altAttr);
     if (!alt.isEmpty())
         return alt;
 
@@ -1909,13 +1946,39 @@ static String accessibleNameForNode(Node* node, Node* labelledbyNode)
     if (!text.isEmpty())
         return text;
     
-    const AtomicString& title = element.fastGetAttribute(titleAttr);
+    const AtomicString& title = element.attributeWithoutSynchronization(titleAttr);
     if (!title.isEmpty())
         return title;
     
     return String();
 }
 
+String AccessibilityNodeObject::accessibilityDescriptionForChildren() const
+{
+    Node* node = this->node();
+    if (!node)
+        return String();
+
+    AXObjectCache* cache = axObjectCache();
+    if (!cache)
+        return String();
+
+    StringBuilder builder;
+    for (Node* child = node->firstChild(); child; child = child->nextSibling()) {
+        if (!is<Element>(child))
+            continue;
+
+        if (AccessibilityObject* axObject = cache->getOrCreate(child)) {
+            String description = axObject->ariaLabeledByAttribute();
+            if (description.isEmpty())
+                description = accessibleNameForNode(child);
+            appendNameToStringBuilder(builder, description);
+        }
+    }
+
+    return builder.toString();
+}
+
 String AccessibilityNodeObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const
 {
     StringBuilder builder;
@@ -1954,7 +2017,9 @@ bool AccessibilityNodeObject::hasAttributesRequiredForInclusion() const
     if (AccessibilityObject::hasAttributesRequiredForInclusion())
         return true;
 
-    if (!ariaAccessibilityDescription().isEmpty())
+    // Avoid calculating the actual description here, which is expensive.
+    // This means there might be more accessible elements in the tree if the labelledBy points to invalid elements, but that shouldn't cause any real problems.
+    if (getAttribute(aria_labelledbyAttr).length() || getAttribute(aria_labeledbyAttr).length() || getAttribute(aria_labelAttr).length())
         return true;
 
     return false;
@@ -1983,34 +2048,97 @@ bool AccessibilityNodeObject::canSetFocusAttribute() const
     return element.supportsFocus();
 }
 
+bool AccessibilityNodeObject::canSetValueAttribute() const
+{
+    Node* node = this->node();
+    if (!node)
+        return false;
+
+    // The host-language readonly attribute trumps aria-readonly.
+    if (is<HTMLTextAreaElement>(*node))
+        return !downcast<HTMLTextAreaElement>(*node).isReadOnly();
+    if (is<HTMLInputElement>(*node)) {
+        HTMLInputElement& input = downcast<HTMLInputElement>(*node);
+        if (input.isTextField())
+            return !input.isReadOnly();
+    }
+
+    String readOnly = readOnlyValue();
+    if (!readOnly.isEmpty())
+        return readOnly == "true" ? false : true;
+
+    if (isNonNativeTextControl())
+        return true;
+
+    if (isMeter())
+        return false;
+
+    if (isProgressIndicator() || isSlider())
+        return true;
+
+#if PLATFORM(GTK)
+    // In ATK, input types which support aria-readonly are treated as having a
+    // settable value if the user can modify the widget's value or its state.
+    if (supportsReadOnly())
+        return true;
+
+    if (isRadioButton()) {
+        auto radioGroup = radioGroupAncestor();
+        return radioGroup ? radioGroup->readOnlyValue() != "true" : true;
+    }
+#endif
+
+    if (isWebArea()) {
+        Document* document = this->document();
+        if (!document)
+            return false;
+
+        if (HTMLElement* body = document->bodyOrFrameset()) {
+            if (body->hasEditableStyle())
+                return true;
+        }
+
+        return document->hasEditableStyle();
+    }
+
+    return node->hasEditableStyle();
+}
+
 AccessibilityRole AccessibilityNodeObject::determineAriaRoleAttribute() const
 {
-    const AtomicString& ariaRole = getAttribute(roleAttr);
+    const AtomicString& ariaRole = stringValueForProperty(AXPropertyName::Role);
     if (ariaRole.isNull() || ariaRole.isEmpty())
-        return UnknownRole;
+        return AccessibilityRole::Unknown;
     
     AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole);
 
     // ARIA states if an item can get focus, it should not be presentational.
-    if (role == PresentationalRole && canSetFocusAttribute())
-        return UnknownRole;
+    if (role == AccessibilityRole::Presentational && canSetFocusAttribute())
+        return AccessibilityRole::Unknown;
 
-    if (role == ButtonRole)
+    if (role == AccessibilityRole::Button)
         role = buttonRoleType();
 
-    if (role == TextAreaRole && !ariaIsMultiline())
-        role = TextFieldRole;
+    if (role == AccessibilityRole::TextArea && !ariaIsMultiline())
+        role = AccessibilityRole::TextField;
 
     role = remapAriaRoleDueToParent(role);
     
     // Presentational roles are invalidated by the presence of ARIA attributes.
-    if (role == PresentationalRole && supportsARIAAttributes())
-        role = UnknownRole;
+    if (role == AccessibilityRole::Presentational && supportsARIAAttributes())
+        role = AccessibilityRole::Unknown;
     
-    if (role)
+    // The ARIA spec states, "Authors must give each element with role region a brief label that
+    // describes the purpose of the content in the region." The Core AAM states, "Special case:
+    // if the region does not have an accessible name, do not expose the element as a landmark.
+    // Use the native host language role of the element instead."
+    if (role == AccessibilityRole::LandmarkRegion && !hasProperty(AXPropertyName::Label) && !hasAttribute(aria_labelledbyAttr))
+        role = AccessibilityRole::Unknown;
+
+    if (static_cast<int>(role))
         return role;
 
-    return UnknownRole;
+    return AccessibilityRole::Unknown;
 }
 
 AccessibilityRole AccessibilityNodeObject::ariaRoleAttribute() const
@@ -2025,21 +2153,21 @@ AccessibilityRole AccessibilityNodeObject::remapAriaRoleDueToParent(Accessibilit
     // While inside the call stack of creating an element, we need to avoid accessibilityIsIgnored().
     // https://bugs.webkit.org/show_bug.cgi?id=65174
 
-    if (role != ListBoxOptionRole && role != MenuItemRole)
+    if (role != AccessibilityRole::ListBoxOption && role != AccessibilityRole::MenuItem)
         return role;
     
     for (AccessibilityObject* parent = parentObject(); parent && !parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
         AccessibilityRole parentAriaRole = parent->ariaRoleAttribute();
 
         // Selects and listboxes both have options as child roles, but they map to different roles within WebCore.
-        if (role == ListBoxOptionRole && parentAriaRole == MenuRole)
-            return MenuItemRole;
+        if (role == AccessibilityRole::ListBoxOption && parentAriaRole == AccessibilityRole::Menu)
+            return AccessibilityRole::MenuItem;
         // An aria "menuitem" may map to MenuButton or MenuItem depending on its parent.
-        if (role == MenuItemRole && parentAriaRole == GroupRole)
-            return MenuButtonRole;
+        if (role == AccessibilityRole::MenuItem && parentAriaRole == AccessibilityRole::ApplicationGroup)
+            return AccessibilityRole::MenuButton;
         
         // If the parent had a different role, then we don't need to continue searching up the chain.
-        if (parentAriaRole)
+        if (parentAriaRole != AccessibilityRole::Unknown)
             break;
     }
     
@@ -2050,15 +2178,19 @@ bool AccessibilityNodeObject::canSetSelectedAttribute() const
 {
     // Elements that can be selected
     switch (roleValue()) {
-    case CellRole:
-    case RadioButtonRole:
-    case RowHeaderRole:
-    case RowRole:
-    case TabListRole:
-    case TabRole:
-    case TreeGridRole:
-    case TreeItemRole:
-    case TreeRole:
+    case AccessibilityRole::Cell:
+    case AccessibilityRole::GridCell:
+    case AccessibilityRole::RadioButton:
+    case AccessibilityRole::RowHeader:
+    case AccessibilityRole::Row:
+    case AccessibilityRole::TabList:
+    case AccessibilityRole::Tab:
+    case AccessibilityRole::TreeGrid:
+    case AccessibilityRole::TreeItem:
+    case AccessibilityRole::Tree:
+    case AccessibilityRole::MenuItemCheckbox:
+    case AccessibilityRole::MenuItemRadio:
+    case AccessibilityRole::MenuItem:
         return isEnabled();
     default:
         return false;