AX: Defer attribute computation until needed.
authorcfleizach@apple.com <cfleizach@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 8 Feb 2018 18:10:20 +0000 (18:10 +0000)
committercfleizach@apple.com <cfleizach@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 8 Feb 2018 18:10:20 +0000 (18:10 +0000)
https://bugs.webkit.org/show_bug.cgi?id=182386
<rdar://problem/37115277>

Reviewed by Zalan Bujtas.

Source/WebCore:

Accessibility is doing too much work when handling attribute changes. Here's how we can improve this:
   1) Defer attribute changes while the tree is dirty (and coalesce them).
   2) Don't create AXObjects when an attribute changes unnecessarily. If no client has requested an ax object, it's likely no work needs to be done
         (with the exception of a few attributes like aria-modal)
   3) Stop calculating the entire accessible ARIA label when trying to decide if an element should be ignored. That's generally wasteful and the
         consequence of including more AX elements in the tree is very minimal.

* accessibility/AXObjectCache.cpp:
(WebCore::rendererNeedsDeferredUpdate):
(WebCore::nodeAndRendererAreValid):
(WebCore::AXObjectCache::remove):
(WebCore::AXObjectCache::handleAriaExpandedChange):
(WebCore::AXObjectCache::handleAriaRoleChanged):
(WebCore::AXObjectCache::deferAttributeChangeIfNeeded):
(WebCore::AXObjectCache::shouldProcessAttributeChange):
(WebCore::AXObjectCache::handleAttributeChange):
(WebCore::AXObjectCache::prepareForDocumentDestruction):
(WebCore::AXObjectCache::performDeferredCacheUpdate):
(WebCore::AXObjectCache::deferRecomputeIsIgnoredIfNeeded):
(WebCore::AXObjectCache::deferRecomputeIsIgnored):
(WebCore::AXObjectCache::deferTextChangedIfNeeded):
(WebCore::AXObjectCache::deferSelectedChildrenChangedIfNeeded):
(WebCore::AXObjectCache::handleAttributeChanged): Deleted.
* accessibility/AXObjectCache.h:
(WebCore::AXObjectCache::deferAttributeChangeIfNeeded):
(WebCore::AXObjectCache::handleAttributeChanged): Deleted.
* accessibility/AccessibilityNodeObject.cpp:
(WebCore::AccessibilityNodeObject::hasAttributesRequiredForInclusion const):
* accessibility/AccessibleNode.cpp:
(WebCore::AccessibleNode::notifyAttributeChanged):
* dom/Element.cpp:
(WebCore::Element::attributeChanged):

LayoutTests:

Update tests to reflect new world of delayed attribute handling for accessibility.

* accessibility/canvas-fallback-content.html:
     Make test async so attributes can be checked after deferred handling.
* accessibility/mac/aria-expanded-notifications.html:
     Access elements through AX tree so attribute changes generate notifications.
* accessibility/mac/aria-listbox-selectedchildren-change.html:
     Make test async so attributes can be checked after deferred handling.
* accessibility/mac/aria-menu-item-selected-notification.html:
     Access menu item through AX tree so attribute changes generate notifications.
* accessibility/mac/aria-modal-auto-focus.html:
     Access buttons after delay so attributes have time to be deferred.
* accessibility/mac/element-busy-changed.html:
     Process second attribute change after delay so we generate two notifications.
* accessibility/mac/expanded-notification.html:
     Set attributes after a delay so they generate individual notifications.
* accessibility/notification-listeners.html:
      Access elements through AX tree so attribute changes generate notifications.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@228279 268f45cc-cd09-0410-ab3c-d52691b4dbfc

15 files changed:
LayoutTests/ChangeLog
LayoutTests/accessibility/canvas-fallback-content.html
LayoutTests/accessibility/mac/aria-expanded-notifications.html
LayoutTests/accessibility/mac/aria-listbox-selectedchildren-change.html
LayoutTests/accessibility/mac/aria-menu-item-selected-notification.html
LayoutTests/accessibility/mac/aria-modal-auto-focus.html
LayoutTests/accessibility/mac/element-busy-changed.html
LayoutTests/accessibility/mac/expanded-notification.html
LayoutTests/accessibility/notification-listeners.html
Source/WebCore/ChangeLog
Source/WebCore/accessibility/AXObjectCache.cpp
Source/WebCore/accessibility/AXObjectCache.h
Source/WebCore/accessibility/AccessibilityNodeObject.cpp
Source/WebCore/accessibility/AccessibleNode.cpp
Source/WebCore/dom/Element.cpp

index 45b2b08..74f1082 100644 (file)
@@ -1,3 +1,30 @@
+2018-02-08  Chris Fleizach  <cfleizach@apple.com>
+
+        AX: Defer attribute computation until needed.
+        https://bugs.webkit.org/show_bug.cgi?id=182386
+        <rdar://problem/37115277>
+
+        Reviewed by Zalan Bujtas.
+
+        Update tests to reflect new world of delayed attribute handling for accessibility.
+
+        * accessibility/canvas-fallback-content.html:
+             Make test async so attributes can be checked after deferred handling.
+        * accessibility/mac/aria-expanded-notifications.html:
+             Access elements through AX tree so attribute changes generate notifications.
+        * accessibility/mac/aria-listbox-selectedchildren-change.html:
+             Make test async so attributes can be checked after deferred handling.
+        * accessibility/mac/aria-menu-item-selected-notification.html:
+             Access menu item through AX tree so attribute changes generate notifications.
+        * accessibility/mac/aria-modal-auto-focus.html:
+             Access buttons after delay so attributes have time to be deferred.
+        * accessibility/mac/element-busy-changed.html:
+             Process second attribute change after delay so we generate two notifications.
+        * accessibility/mac/expanded-notification.html:
+             Set attributes after a delay so they generate individual notifications.
+        * accessibility/notification-listeners.html:
+              Access elements through AX tree so attribute changes generate notifications.
+
 2018-02-08  Miguel Gomez  <magomez@igalia.com>
 
         Unreviewed GTK+ gardening after r228270.
index ee41b5e..9185598 100644 (file)
@@ -40,6 +40,7 @@ myelement {
 description("This test makes sure that focusable elements in canvas fallback content are accessible.");
 
 if (window.testRunner && window.accessibilityController) {
+    window.jsTestIsAsync = true;
     window.testRunner.dumpAsText();
 
     function check(id, expectedRole) {
@@ -80,9 +81,15 @@ if (window.testRunner && window.accessibilityController) {
 
     // Check that the role is updated when the element changes.
     document.getElementById('focusable1').setAttribute('role', 'button');
-    check("focusable1", "AXRole: AXButton");
+    setTimeout(function() {
+        check("focusable1", "AXRole: AXButton");
+    }, 1);
+
     document.getElementById('focusable2').setAttribute('role', 'button');
-    check("focusable2", "AXRole: AXButton");
+    setTimeout(function() {
+        check("focusable2", "AXRole: AXButton");
+        finishJSTest();
+    }, 1);
 }
 
 </script>
index 92ea054..953e5a6 100644 (file)
@@ -58,6 +58,9 @@
         var addedNotification = accessibilityController.addNotificationListener(notifyCallback);
         shouldBe("addedNotification", "true");
 
+        accessibilityController.accessibleElementById("tree0");
+        accessibilityController.accessibleElementById("tree0_item0");
+
         // the first aria-expanded should generate row count, row collapsed.
         document.getElementById("tree0_item0").setAttribute("aria-expanded", "false");
 
index ac84aa9..0e92664 100644 (file)
@@ -5,7 +5,7 @@
 </head>
 <body id="body">
 
-<div role="group" tabindex=0 id="listbox" role="listbox">
+<div tabindex=0 id="listbox" role="listbox">
 <div id="option1" role="option" aria-selected="true">Option</div>
 <div id="option2" role="option">Option</div>
 <div id="option3" role="option">Option</div>
     if (window.accessibilityController) {
         jsTestIsAsync = true;
 
-        document.getElementById("listbox").focus();
-        listbox = window.accessibilityController.focusedElement;
+        listbox = accessibilityController.accessibleElementById("listbox");
 
         var addedNotification = window.accessibilityController.addNotificationListener(ariaCallback);
         shouldBe("addedNotification", "true");
 
         // These should each trigger a notification that the selected children changed.
         document.getElementById("option2").setAttribute("aria-selected", "true");
-        document.getElementById("option2").setAttribute("aria-selected", "false");
+        setTimeout(function() {
+            document.getElementById("option2").setAttribute("aria-selected", "false");
+        }, 1);
     }
 
 </script>
index d1d48c2..503ef2b 100644 (file)
@@ -39,7 +39,7 @@
         window.jsTestIsAsync = true;
 
         var addedNotification = accessibilityController.addNotificationListener(ariaCallback);
-        accessibilityController.rootElement;
+        accessibilityController.accessibleElementById("menu");
 
         shouldBe("addedNotification", "true");
 
index 5d5b5c3..16c2654 100644 (file)
             
             // 2. Click the new button, dialog2 shows and focus should move to the close button.
             document.getElementById("new").click();
-            closeBtn = accessibilityController.accessibleElementById("close");
             setTimeout(function(){ 
+                closeBtn = accessibilityController.accessibleElementById("close");
                 shouldBeTrue("closeBtn.isFocused");
-                
+                  
                 // 3. Click the close button, dialog2 closes and focus should go back to the
                 // first focusable child of dialog1.
                 document.getElementById("close").click();
-                okBtn = accessibilityController.accessibleElementById("ok");
                 setTimeout(function(){
+                    okBtn = accessibilityController.accessibleElementById("ok");
                     shouldBeTrue("okBtn.isFocused");
                     finishJSTest();
-                }, 50);
-            }, 50);
-        }, 50);
+                }, 100);
+            }, 100);
+        }, 100);
     }
     
     function backgroundAccessible() {
@@ -92,4 +92,4 @@
 
 <script src="../../resources/js-test-post.js"></script>
 </body>
-</html>
\ No newline at end of file
+</html>
index 3a23877..b0e725c 100644 (file)
         // Toggle through both busy state transitions.
         var busyElement = document.getElementById("body");
         busyElement.setAttribute("aria-busy", "true");
-        busyElement.setAttribute("aria-busy", "false");
+
+        setTimeout(function() {
+            busyElement.setAttribute("aria-busy", "false");
+        }, 1);
     }
 </script>
 
index fd55b95..3f08124 100644 (file)
         window.jsTestIsAsync = true;
 
         var addedNotification = accessibilityController.addNotificationListener(ariaCallback);
-        debug("Initial expanded status: " + accessibilityController.accessibleElementById("button").isExpanded);
+        var button = accessibilityController.accessibleElementById("button");
+        debug("Initial expanded status: " + button.isExpanded);
+
+        document.getElementById("button").setAttribute("aria-expanded", "true");
 
         setTimeout(function() {
-            document.getElementById("button").setAttribute("aria-expanded", "true");
-            setTimeout(function() {
-                document.getElementById("button").setAttribute("aria-expanded", "false");
-            }, 10);
+            document.getElementById("button").setAttribute("aria-expanded", "false");
         }, 10);
     }
 
index 659e0a0..3716147 100644 (file)
@@ -45,6 +45,10 @@ function runTest() {
         });
     }
 
+    // Ensure these elements exist in the AX tree otherwise notifications won't be generated.
+    accessibilityController.accessibleElementById("select");
+    accessibilityController.accessibleElementById("slider");
+
     // This should trigger a "invalid status changed" notification on the select.
     document.getElementById("select").setAttribute("aria-invalid", "true");
 
index 5156411..d26f58c 100644 (file)
@@ -1,3 +1,44 @@
+2018-02-08  Chris Fleizach  <cfleizach@apple.com>
+
+        AX: Defer attribute computation until needed.
+        https://bugs.webkit.org/show_bug.cgi?id=182386
+        <rdar://problem/37115277>
+
+        Reviewed by Zalan Bujtas.
+
+        Accessibility is doing too much work when handling attribute changes. Here's how we can improve this:
+           1) Defer attribute changes while the tree is dirty (and coalesce them). 
+           2) Don't create AXObjects when an attribute changes unnecessarily. If no client has requested an ax object, it's likely no work needs to be done
+                 (with the exception of a few attributes like aria-modal)
+           3) Stop calculating the entire accessible ARIA label when trying to decide if an element should be ignored. That's generally wasteful and the
+                 consequence of including more AX elements in the tree is very minimal.
+
+        * accessibility/AXObjectCache.cpp:
+        (WebCore::rendererNeedsDeferredUpdate):
+        (WebCore::nodeAndRendererAreValid):
+        (WebCore::AXObjectCache::remove):
+        (WebCore::AXObjectCache::handleAriaExpandedChange):
+        (WebCore::AXObjectCache::handleAriaRoleChanged):
+        (WebCore::AXObjectCache::deferAttributeChangeIfNeeded):
+        (WebCore::AXObjectCache::shouldProcessAttributeChange):
+        (WebCore::AXObjectCache::handleAttributeChange):
+        (WebCore::AXObjectCache::prepareForDocumentDestruction):
+        (WebCore::AXObjectCache::performDeferredCacheUpdate):
+        (WebCore::AXObjectCache::deferRecomputeIsIgnoredIfNeeded):
+        (WebCore::AXObjectCache::deferRecomputeIsIgnored):
+        (WebCore::AXObjectCache::deferTextChangedIfNeeded):
+        (WebCore::AXObjectCache::deferSelectedChildrenChangedIfNeeded):
+        (WebCore::AXObjectCache::handleAttributeChanged): Deleted.
+        * accessibility/AXObjectCache.h:
+        (WebCore::AXObjectCache::deferAttributeChangeIfNeeded):
+        (WebCore::AXObjectCache::handleAttributeChanged): Deleted.
+        * accessibility/AccessibilityNodeObject.cpp:
+        (WebCore::AccessibilityNodeObject::hasAttributesRequiredForInclusion const):
+        * accessibility/AccessibleNode.cpp:
+        (WebCore::AccessibleNode::notifyAttributeChanged):
+        * dom/Element.cpp:
+        (WebCore::Element::attributeChanged): 
+
 2018-02-08  Chris Dumez  <cdumez@apple.com>
 
         Unreviewed, tiny partial rollout of r228260 as it caused some worker failures
index 4034292..e23b58f 100644 (file)
@@ -120,7 +120,23 @@ using namespace HTMLNames;
 static const Seconds accessibilityPasswordValueChangeNotificationInterval { 25_ms };
 static const Seconds accessibilityLiveRegionChangedNotificationInterval { 20_ms };
 static const Seconds accessibilityFocusModalNodeNotificationInterval { 50_ms };
+    
+static bool rendererNeedsDeferredUpdate(const RenderObject& renderer)
+{
+    ASSERT(!renderer.beingDestroyed());
+    auto& document = renderer.document();
+    return renderer.needsLayout() || document.needsStyleRecalc() || document.inRenderTreeUpdate() || (document.view() && document.view()->layoutContext().isInRenderTreeLayout());
+}
 
+static bool nodeAndRendererAreValid(Node* node)
+{
+    if (!node)
+        return false;
+    
+    auto* renderer = node->renderer();
+    return renderer && !renderer->beingDestroyed();
+}
+    
 AccessibilityObjectInclusion AXComputedObjectAttributeCache::getIgnored(AXID id) const
 {
     auto it = m_idMapping.find(id);
@@ -723,6 +739,7 @@ void AXObjectCache::remove(Node& node)
         m_deferredRecomputeIsIgnoredList.remove(downcast<Element>(&node));
         m_deferredSelectedChildredChangedList.remove(downcast<Element>(&node));
         m_deferredTextFormControlValue.remove(downcast<Element>(&node));
+        m_deferredAttributeChange.remove(downcast<Element>(&node));
     }
     m_deferredTextChangedList.remove(&node);
     removeNodeForUse(node);
@@ -1402,7 +1419,7 @@ void AXObjectCache::handleScrollbarUpdate(ScrollView* view)
     
 void AXObjectCache::handleAriaExpandedChange(Node* node)
 {
-    if (AccessibilityObject* obj = getOrCreate(node))
+    if (AccessibilityObject* obj = get(node))
         obj->handleAriaExpandedChanged();
 }
     
@@ -1416,18 +1433,48 @@ void AXObjectCache::handleAriaRoleChanged(Node* node)
 {
     stopCachingComputedObjectAttributes();
 
-    if (AccessibilityObject* obj = getOrCreate(node)) {
+    // Don't make an AX object unless it's needed
+    if (AccessibilityObject* obj = get(node)) {
         obj->updateAccessibilityRole();
         obj->notifyIfIgnoredValueChanged();
     }
 }
 
-void AXObjectCache::handleAttributeChanged(const QualifiedName& attrName, Element* element)
+void AXObjectCache::deferAttributeChangeIfNeeded(const QualifiedName& attrName, Element* element)
+{
+    if (nodeAndRendererAreValid(element) && rendererNeedsDeferredUpdate(*element->renderer()))
+        m_deferredAttributeChange.add(element, attrName);
+    else
+        handleAttributeChange(attrName, element);
+}
+    
+bool AXObjectCache::shouldProcessAttributeChange(const QualifiedName& attrName, Element* element)
+{
+    if (!element)
+        return false;
+    
+    // aria-modal ends up affecting sub-trees that are being shown/hidden so it's likely that
+    // an AT would not have accessed this node yet.
+    if (attrName == aria_modalAttr)
+        return true;
+    
+    // If an AXObject has yet to be created, then there's no need to process attribute changes.
+    // Some of these notifications are processed on the parent, so allow that to proceed as well
+    if (get(element) || get(element->parentNode()))
+        return true;
+
+    return false;
+}
+    
+void AXObjectCache::handleAttributeChange(const QualifiedName& attrName, Element* element)
 {
+    if (!shouldProcessAttributeChange(attrName, element))
+        return;
+    
     if (attrName == roleAttr)
         handleAriaRoleChanged(element);
     else if (attrName == altAttr || attrName == titleAttr)
-        deferTextChangedIfNeeded(element);
+        textChanged(element);
     else if (attrName == forAttr && is<HTMLLabelElement>(*element))
         labelChanged(element);
 
@@ -1441,7 +1488,7 @@ void AXObjectCache::handleAttributeChanged(const QualifiedName& attrName, Elemen
     else if (attrName == aria_valuenowAttr || attrName == aria_valuetextAttr)
         postNotification(element, AXObjectCache::AXValueChanged);
     else if (attrName == aria_labelAttr || attrName == aria_labeledbyAttr || attrName == aria_labelledbyAttr)
-        deferTextChangedIfNeeded(element);
+        textChanged(element);
     else if (attrName == aria_checkedAttr)
         checkedStateChanged(element);
     else if (attrName == aria_selectedAttr)
@@ -2760,6 +2807,7 @@ void AXObjectCache::prepareForDocumentDestruction(const Document& document)
     filterListForRemoval(m_deferredTextChangedList, document, nodesToRemove);
     filterListForRemoval(m_deferredSelectedChildredChangedList, document, nodesToRemove);
     filterMapForRemoval(m_deferredTextFormControlValue, document, nodesToRemove);
+    filterMapForRemoval(m_deferredAttributeChange, document, nodesToRemove);
 
     for (auto* node : nodesToRemove)
         remove(*node);
@@ -2799,37 +2847,27 @@ void AXObjectCache::performDeferredCacheUpdate()
         postTextReplacementNotificationForTextControl(textFormControlElement, deferredFormControlContext.value, textFormControlElement.innerTextValue());
     }
     m_deferredTextFormControlValue.clear();
-}
 
-static bool rendererNeedsDeferredUpdate(RenderObject& renderer)
-{
-    ASSERT(!renderer.beingDestroyed());
-    auto& document = renderer.document();
-    return renderer.needsLayout() || document.needsStyleRecalc() || document.inRenderTreeUpdate() || (document.view() && document.view()->layoutContext().isInRenderTreeLayout());
+    for (auto& deferredAttributeChangeContext : m_deferredAttributeChange)
+        handleAttributeChange(deferredAttributeChangeContext.value, deferredAttributeChangeContext.key);
+    m_deferredAttributeChange.clear();
 }
-
+    
 void AXObjectCache::deferRecomputeIsIgnoredIfNeeded(Element* element)
 {
-    if (!element)
+    if (!nodeAndRendererAreValid(element))
         return;
-
-    auto* renderer = element->renderer();
-    if (!renderer || renderer->beingDestroyed())
-        return;
-
-    if (rendererNeedsDeferredUpdate(*renderer)) {
+    
+    if (rendererNeedsDeferredUpdate(*element->renderer())) {
         m_deferredRecomputeIsIgnoredList.add(element);
         return;
     }
-    recomputeIsIgnored(renderer);
+    recomputeIsIgnored(element->renderer());
 }
 
 void AXObjectCache::deferRecomputeIsIgnored(Element* element)
 {
-    if (!element)
-        return;
-
-    if (element->renderer() && element->renderer()->beingDestroyed())
+    if (!nodeAndRendererAreValid(element))
         return;
 
     m_deferredRecomputeIsIgnoredList.add(element);
@@ -2837,14 +2875,10 @@ void AXObjectCache::deferRecomputeIsIgnored(Element* element)
 
 void AXObjectCache::deferTextChangedIfNeeded(Node* node)
 {
-    if (!node)
-        return;
-
-    auto* renderer = node->renderer();
-    if (renderer && renderer->beingDestroyed())
+    if (!nodeAndRendererAreValid(node))
         return;
 
-    if (renderer && rendererNeedsDeferredUpdate(*renderer)) {
+    if (rendererNeedsDeferredUpdate(*node->renderer())) {
         m_deferredTextChangedList.add(node);
         return;
     }
@@ -2853,11 +2887,10 @@ void AXObjectCache::deferTextChangedIfNeeded(Node* node)
 
 void AXObjectCache::deferSelectedChildrenChangedIfNeeded(Element& selectElement)
 {
-    auto* renderer = selectElement.renderer();
-    if (renderer && renderer->beingDestroyed())
+    if (!nodeAndRendererAreValid(&selectElement))
         return;
-    
-    if (renderer && rendererNeedsDeferredUpdate(*renderer)) {
+
+    if (rendererNeedsDeferredUpdate(*selectElement.renderer())) {
         m_deferredSelectedChildredChangedList.add(&selectElement);
         return;
     }
index ffecf6b..205a5a1 100644 (file)
@@ -185,7 +185,7 @@ public:
     void handleModalChange(Node*);
     Node* modalNode();
 
-    void handleAttributeChanged(const QualifiedName& attrName, Element*);
+    void deferAttributeChangeIfNeeded(const QualifiedName&, Element*);
     void recomputeIsIgnored(RenderObject* renderer);
 
 #if HAVE(ACCESSIBILITY)
@@ -409,6 +409,8 @@ private:
     void handleMenuOpened(Node*);
     void handleLiveRegionCreated(Node*);
     void handleMenuItemSelected(Node*);
+    void handleAttributeChange(const QualifiedName&, Element*);
+    bool shouldProcessAttributeChange(const QualifiedName&, Element*);
     
     // aria-modal related
     void findModalNodes();
@@ -446,6 +448,7 @@ private:
     ListHashSet<Node*> m_deferredTextChangedList;
     ListHashSet<Element*> m_deferredSelectedChildredChangedList;
     HashMap<Element*, String> m_deferredTextFormControlValue;
+    HashMap<Element*, QualifiedName> m_deferredAttributeChange;
     bool m_isSynchronizingSelection { false };
     bool m_performingDeferredCacheUpdate { false };
 };
@@ -507,7 +510,9 @@ inline void AXObjectCache::handleActiveDescendantChanged(Node*) { }
 inline void AXObjectCache::handleAriaExpandedChange(Node*) { }
 inline void AXObjectCache::handleModalChange(Node*) { }
 inline void AXObjectCache::handleAriaRoleChanged(Node*) { }
-inline void AXObjectCache::handleAttributeChanged(const QualifiedName&, Element*) { }
+inline void AXObjectCache::deferAttributeChangeIfNeeded(const QualifiedName&, Element*) { }
+inline void AXObjectCache::handleAttributeChange(const QualifiedName&, Element*) { }
+inline bool AXObjectCache::shouldProcessAttributeChange(const QualifiedName&, Element*) { return false; }
 inline void AXObjectCache::handleFocusedUIElementChanged(Node*, Node*) { }
 inline void AXObjectCache::handleScrollbarUpdate(ScrollView*) { }
 inline void AXObjectCache::handleScrolledToAnchor(const Node*) { }
index f94167b..b072efd 100644 (file)
@@ -2017,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;
index 0f33bee..e1c6b1a 100644 (file)
@@ -379,7 +379,7 @@ Vector<RefPtr<Element>> AccessibleNode::effectiveElementsValueForElement(Element
 void AccessibleNode::notifyAttributeChanged(const WebCore::QualifiedName& name)
 {
     if (AXObjectCache* cache = m_ownerElement.document().axObjectCache())
-        cache->handleAttributeChanged(name, &m_ownerElement);
+        cache->deferAttributeChangeIfNeeded(name, &m_ownerElement);
 }
 
 RefPtr<AccessibleNode> AccessibleNode::activeDescendant() const
index 8b0ab7b..8567f60 100644 (file)
@@ -1389,7 +1389,7 @@ void Element::attributeChanged(const QualifiedName& name, const AtomicString& ol
     invalidateNodeListAndCollectionCachesInAncestorsForAttribute(name);
 
     if (AXObjectCache* cache = document().existingAXObjectCache())
-        cache->handleAttributeChanged(name, this);
+        cache->deferAttributeChangeIfNeeded(name, this);
 }
 
 template <typename CharacterType>