Title: [216419] trunk
Revision
216419
Author
za...@apple.com
Date
2017-05-08 08:33:47 -0700 (Mon, 08 May 2017)

Log Message

Ensure clean tree before AX cache update.
https://bugs.webkit.org/show_bug.cgi?id=171546
<rdar://problem/31934942>

Source/WebCore:

While updating an accessibility object state, we might
perform unintentional style updates. This style update could
end up destroying renderes that are still referenced by function calls
on the callstack.
To avoid that, AXObjectCache should operate on a clean tree only.

Reviewed by Chris Fleizach.

Test: accessibility/crash-when-render-tree-is-not-clean.html

* accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::checkedStateChanged):
(WebCore::AXObjectCache::selectedChildrenChanged):
(WebCore::AXObjectCache::handleAriaExpandedChange):
(WebCore::AXObjectCache::handleActiveDescendantChanged):
(WebCore::AXObjectCache::handleAriaRoleChanged):
(WebCore::AXObjectCache::handleAttributeChanged):
(WebCore::AXObjectCache::handleAriaModalChange):
(WebCore::AXObjectCache::labelChanged):
* accessibility/AXObjectCache.h:
(WebCore::AXObjectCache::checkedStateChanged):
(WebCore::AXObjectCache::handleActiveDescendantChanged):
(WebCore::AXObjectCache::handleAriaExpandedChange):
(WebCore::AXObjectCache::handleAriaRoleChanged):
(WebCore::AXObjectCache::handleAriaModalChange):
(WebCore::AXObjectCache::handleAttributeChanged):
(WebCore::AXObjectCache::selectedChildrenChanged):
* accessibility/AccessibilityRenderObject.cpp:
(WebCore::AccessibilityRenderObject::handleAriaExpandedChanged):
* dom/Element.cpp:
(WebCore::Element::attributeChanged):
* html/HTMLInputElement.cpp:
(WebCore::HTMLInputElement::setChecked):

LayoutTests:

Reviewed by Chris Fleizach.

* accessibility/crash-when-render-tree-is-not-clean.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (216418 => 216419)


--- trunk/LayoutTests/ChangeLog	2017-05-08 15:26:38 UTC (rev 216418)
+++ trunk/LayoutTests/ChangeLog	2017-05-08 15:33:47 UTC (rev 216419)
@@ -1,3 +1,13 @@
+2017-05-06  Zalan Bujtas  <za...@apple.com>
+
+        Ensure clean tree before AX cache update.
+        https://bugs.webkit.org/show_bug.cgi?id=171546
+        <rdar://problem/31934942>
+
+        Reviewed by Chris Fleizach.
+
+        * accessibility/crash-when-render-tree-is-not-clean.html: Added.
+
 2017-05-08  Myles C. Maxfield  <mmaxfi...@apple.com>
 
         Unprefix unicode-bidi CSS values

Added: trunk/LayoutTests/accessibility/crash-when-render-tree-is-not-clean-expected.txt (0 => 216419)


--- trunk/LayoutTests/accessibility/crash-when-render-tree-is-not-clean-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/accessibility/crash-when-render-tree-is-not-clean-expected.txt	2017-05-08 15:33:47 UTC (rev 216419)
@@ -0,0 +1,2 @@
+Pass if no crash or assert.
+

Added: trunk/LayoutTests/accessibility/crash-when-render-tree-is-not-clean.html (0 => 216419)


--- trunk/LayoutTests/accessibility/crash-when-render-tree-is-not-clean.html	                        (rev 0)
+++ trunk/LayoutTests/accessibility/crash-when-render-tree-is-not-clean.html	2017-05-08 15:33:47 UTC (rev 216419)
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>This tests that we properly update the tree before updating accessibility cache.</title>
+<script>
+if (window.accessibilityController)
+    accessibilityController.accessibleElementById("outer");
+if (window.testRunner)
+    testRunner.dumpAsText();
+</script>
+</head>
+<body>
+Pass if no crash or assert.
+<div id=outer><div id=inner>foobar</div></div>
+<script>
+  inner.style.display = "none";
+  outer.setAttribute("aria-labeledby", "inner");
+</script>
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (216418 => 216419)


--- trunk/Source/WebCore/ChangeLog	2017-05-08 15:26:38 UTC (rev 216418)
+++ trunk/Source/WebCore/ChangeLog	2017-05-08 15:33:47 UTC (rev 216419)
@@ -1,3 +1,43 @@
+2017-05-06  Zalan Bujtas  <za...@apple.com>
+
+        Ensure clean tree before AX cache update.
+        https://bugs.webkit.org/show_bug.cgi?id=171546
+        <rdar://problem/31934942>
+
+        While updating an accessibility object state, we might
+        perform unintentional style updates. This style update could
+        end up destroying renderes that are still referenced by function calls 
+        on the callstack.
+        To avoid that, AXObjectCache should operate on a clean tree only. 
+
+        Reviewed by Chris Fleizach.
+
+        Test: accessibility/crash-when-render-tree-is-not-clean.html
+
+        * accessibility/AXObjectCache.cpp:
+        (WebCore::AXObjectCache::checkedStateChanged):
+        (WebCore::AXObjectCache::selectedChildrenChanged):
+        (WebCore::AXObjectCache::handleAriaExpandedChange):
+        (WebCore::AXObjectCache::handleActiveDescendantChanged):
+        (WebCore::AXObjectCache::handleAriaRoleChanged):
+        (WebCore::AXObjectCache::handleAttributeChanged):
+        (WebCore::AXObjectCache::handleAriaModalChange):
+        (WebCore::AXObjectCache::labelChanged):
+        * accessibility/AXObjectCache.h:
+        (WebCore::AXObjectCache::checkedStateChanged):
+        (WebCore::AXObjectCache::handleActiveDescendantChanged):
+        (WebCore::AXObjectCache::handleAriaExpandedChange):
+        (WebCore::AXObjectCache::handleAriaRoleChanged):
+        (WebCore::AXObjectCache::handleAriaModalChange):
+        (WebCore::AXObjectCache::handleAttributeChanged):
+        (WebCore::AXObjectCache::selectedChildrenChanged):
+        * accessibility/AccessibilityRenderObject.cpp:
+        (WebCore::AccessibilityRenderObject::handleAriaExpandedChanged):
+        * dom/Element.cpp:
+        (WebCore::Element::attributeChanged):
+        * html/HTMLInputElement.cpp:
+        (WebCore::HTMLInputElement::setChecked):
+
 2017-05-08  Myles C. Maxfield  <mmaxfi...@apple.com>
 
         Unprefix unicode-bidi CSS values

Modified: trunk/Source/WebCore/accessibility/AXObjectCache.cpp (216418 => 216419)


--- trunk/Source/WebCore/accessibility/AXObjectCache.cpp	2017-05-08 15:26:38 UTC (rev 216418)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.cpp	2017-05-08 15:33:47 UTC (rev 216419)
@@ -1002,9 +1002,10 @@
         postPlatformNotification(object, notification);
 }
 
-void AXObjectCache::checkedStateChanged(Node* node)
+void AXObjectCache::checkedStateChanged(Element& element)
 {
-    postNotification(node, AXObjectCache::AXCheckedStateChanged);
+    element.document().updateStyleIfNeeded();
+    postNotification(&element, AXObjectCache::AXCheckedStateChanged);
 }
 
 void AXObjectCache::handleMenuItemSelected(Node* node)
@@ -1027,13 +1028,14 @@
     platformHandleFocusedUIElementChanged(oldNode, newNode);
 }
     
-void AXObjectCache::selectedChildrenChanged(Node* node)
+void AXObjectCache::selectedChildrenChanged(Element& element)
 {
-    handleMenuItemSelected(node);
+    element.document().updateStyleIfNeeded();
+    handleMenuItemSelected(&element);
     
     // postTarget is TargetObservableParent so that you can pass in any child of an element and it will go up the parent tree
     // to find the container which should send out the notification.
-    postNotification(node, AXSelectedChildrenChanged, TargetObservableParent);
+    postNotification(&element, AXSelectedChildrenChanged, TargetObservableParent);
 }
 
 void AXObjectCache::selectedChildrenChanged(RenderObject* renderer)
@@ -1402,35 +1404,39 @@
     }
 }
     
-void AXObjectCache::handleAriaExpandedChange(Node* node)
+void AXObjectCache::handleAriaExpandedChange(Element& element)
 {
-    if (AccessibilityObject* obj = getOrCreate(node))
+    ASSERT_WITH_SECURITY_IMPLICATION(!element.needsStyleRecalc());
+    if (AccessibilityObject* obj = getOrCreate(&element))
         obj->handleAriaExpandedChanged();
 }
     
-void AXObjectCache::handleActiveDescendantChanged(Node* node)
+void AXObjectCache::handleActiveDescendantChanged(Element& element)
 {
-    if (AccessibilityObject* obj = getOrCreate(node))
+    ASSERT_WITH_SECURITY_IMPLICATION(!element.needsStyleRecalc());
+    if (AccessibilityObject* obj = getOrCreate(&element))
         obj->handleActiveDescendantChanged();
 }
 
-void AXObjectCache::handleAriaRoleChanged(Node* node)
+void AXObjectCache::handleAriaRoleChanged(Element& element)
 {
+    ASSERT_WITH_SECURITY_IMPLICATION(!element.needsStyleRecalc());
     stopCachingComputedObjectAttributes();
 
-    if (AccessibilityObject* obj = getOrCreate(node)) {
+    if (AccessibilityObject* obj = getOrCreate(&element)) {
         obj->updateAccessibilityRole();
         obj->notifyIfIgnoredValueChanged();
     }
 }
 
-void AXObjectCache::handleAttributeChanged(const QualifiedName& attrName, Element* element)
+void AXObjectCache::handleAttributeChanged(const QualifiedName& attrName, Element& element)
 {
+    element.document().updateStyleIfNeeded();
     if (attrName == roleAttr)
         handleAriaRoleChanged(element);
     else if (attrName == altAttr || attrName == titleAttr)
-        textChanged(element);
-    else if (attrName == forAttr && is<HTMLLabelElement>(*element))
+        textChanged(&element);
+    else if (attrName == forAttr && is<HTMLLabelElement>(element))
         labelChanged(element);
 
     if (!attrName.localName().string().startsWith("aria-"))
@@ -1439,11 +1445,11 @@
     if (attrName == aria_activedescendantAttr)
         handleActiveDescendantChanged(element);
     else if (attrName == aria_busyAttr)
-        postNotification(element, AXObjectCache::AXElementBusyChanged);
+        postNotification(&element, AXObjectCache::AXElementBusyChanged);
     else if (attrName == aria_valuenowAttr || attrName == aria_valuetextAttr)
-        postNotification(element, AXObjectCache::AXValueChanged);
+        postNotification(&element, AXObjectCache::AXValueChanged);
     else if (attrName == aria_labelAttr || attrName == aria_labeledbyAttr || attrName == aria_labelledbyAttr)
-        textChanged(element);
+        textChanged(&element);
     else if (attrName == aria_checkedAttr)
         checkedStateChanged(element);
     else if (attrName == aria_selectedAttr)
@@ -1451,34 +1457,32 @@
     else if (attrName == aria_expandedAttr)
         handleAriaExpandedChange(element);
     else if (attrName == aria_hiddenAttr)
-        childrenChanged(element->parentNode(), element);
+        childrenChanged(element.parentNode(), &element);
     else if (attrName == aria_invalidAttr)
-        postNotification(element, AXObjectCache::AXInvalidStatusChanged);
+        postNotification(&element, AXObjectCache::AXInvalidStatusChanged);
     else if (attrName == aria_modalAttr)
         handleAriaModalChange(element);
     else if (attrName == aria_currentAttr)
-        postNotification(element, AXObjectCache::AXCurrentChanged);
+        postNotification(&element, AXObjectCache::AXCurrentChanged);
     else
-        postNotification(element, AXObjectCache::AXAriaAttributeChanged);
+        postNotification(&element, AXObjectCache::AXAriaAttributeChanged);
 }
 
-void AXObjectCache::handleAriaModalChange(Node* node)
+void AXObjectCache::handleAriaModalChange(Element& element)
 {
-    if (!is<Element>(node))
+    ASSERT_WITH_SECURITY_IMPLICATION(!element.needsStyleRecalc());
+    if (!nodeHasRole(&element, "dialog") && !nodeHasRole(&element, "alertdialog"))
         return;
     
-    if (!nodeHasRole(node, "dialog") && !nodeHasRole(node, "alertdialog"))
-        return;
-    
     stopCachingComputedObjectAttributes();
-    if (equalLettersIgnoringASCIICase(downcast<Element>(*node).attributeWithoutSynchronization(aria_modalAttr), "true")) {
+    if (equalLettersIgnoringASCIICase(element.attributeWithoutSynchronization(aria_modalAttr), "true")) {
         // Add the newly modified node to the modal nodes set, and set it to be the current valid aria modal node.
         // We will recompute the current valid aria modal node in ariaModalNode() when this node is not visible.
-        m_ariaModalNodesSet.add(node);
-        m_currentAriaModalNode = node;
+        m_ariaModalNodesSet.add(&element);
+        m_currentAriaModalNode = &element;
     } else {
         // Remove the node from the modal nodes set. There might be other visible modal nodes, so we recompute here.
-        m_ariaModalNodesSet.remove(node);
+        m_ariaModalNodesSet.remove(&element);
         updateCurrentAriaModalNode();
     }
     if (m_currentAriaModalNode)
@@ -1487,10 +1491,11 @@
     startCachingComputedObjectAttributesUntilTreeMutates();
 }
 
-void AXObjectCache::labelChanged(Element* element)
+void AXObjectCache::labelChanged(Element& element)
 {
-    ASSERT(is<HTMLLabelElement>(*element));
-    HTMLElement* correspondingControl = downcast<HTMLLabelElement>(*element).control();
+    ASSERT_WITH_SECURITY_IMPLICATION(!element.needsStyleRecalc());
+    ASSERT(is<HTMLLabelElement>(element));
+    HTMLElement* correspondingControl = downcast<HTMLLabelElement>(element).control();
     textChanged(correspondingControl);
 }
 

Modified: trunk/Source/WebCore/accessibility/AXObjectCache.h (216418 => 216419)


--- trunk/Source/WebCore/accessibility/AXObjectCache.h	2017-05-08 15:26:38 UTC (rev 216418)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.h	2017-05-08 15:33:47 UTC (rev 216419)
@@ -164,8 +164,8 @@
     void childrenChanged(Node*, Node* newChild = nullptr);
     void childrenChanged(RenderObject*, RenderObject* newChild = nullptr);
     void childrenChanged(AccessibilityObject*);
-    void checkedStateChanged(Node*);
-    void selectedChildrenChanged(Node*);
+    void checkedStateChanged(Element&);
+    void selectedChildrenChanged(Element&);
     void selectedChildrenChanged(RenderObject*);
     // Called by a node when text or a text equivalent (e.g. alt) attribute is changed.
     void textChanged(Node*);
@@ -173,17 +173,13 @@
     // Called when a node has just been attached, so we can make sure we have the right subclass of AccessibilityObject.
     void updateCacheAfterNodeIsAttached(Node*);
 
-    void handleActiveDescendantChanged(Node*);
-    void handleAriaRoleChanged(Node*);
     void handleFocusedUIElementChanged(Node* oldFocusedNode, Node* newFocusedNode);
     void handleScrolledToAnchor(const Node* anchorNode);
-    void handleAriaExpandedChange(Node*);
     void handleScrollbarUpdate(ScrollView*);
-    
-    void handleAriaModalChange(Node*);
+
     Node* ariaModalNode();
 
-    void handleAttributeChanged(const QualifiedName& attrName, Element*);
+    void handleAttributeChanged(const QualifiedName& attrName, Element&);
     void recomputeIsIgnored(RenderObject* renderer);
 
 #if HAVE(ACCESSIBILITY)
@@ -346,7 +342,6 @@
 
     void frameLoadingEventPlatformNotification(AccessibilityObject*, AXLoadingEvent);
     void textChanged(AccessibilityObject*);
-    void labelChanged(Element*);
 
     // This is a weak reference cache for knowing if Nodes used by TextMarkers are valid.
     void setNodeInUse(Node* n) { m_textMarkerNodes.add(n); }
@@ -403,6 +398,12 @@
     void updateCurrentAriaModalNode();
     bool isNodeVisible(Node*) const;
 
+    void handleActiveDescendantChanged(Element&);
+    void handleAriaRoleChanged(Element&);
+    void handleAriaExpandedChange(Element&);
+    void handleAriaModalChange(Element&);
+    void labelChanged(Element&);
+
     Document& m_document;
     HashMap<AXID, RefPtr<AccessibilityObject>> m_objects;
     HashMap<RenderObject*, AXID> m_renderObjectMapping;
@@ -474,7 +475,7 @@
 inline const Element* AXObjectCache::rootAXEditableElement(const Node*) { return nullptr; }
 inline Node* AXObjectCache::ariaModalNode() { return nullptr; }
 inline void AXObjectCache::attachWrapper(AccessibilityObject*) { }
-inline void AXObjectCache::checkedStateChanged(Node*) { }
+inline void AXObjectCache::checkedStateChanged(Element&) { }
 inline void AXObjectCache::childrenChanged(RenderObject*, RenderObject*) { }
 inline void AXObjectCache::childrenChanged(Node*, Node*) { }
 inline void AXObjectCache::childrenChanged(AccessibilityObject*) { }
@@ -485,13 +486,13 @@
 inline void AXObjectCache::detachWrapper(AccessibilityObject*, AccessibilityDetachmentType) { }
 inline void AXObjectCache::frameLoadingEventNotification(Frame*, AXLoadingEvent) { }
 inline void AXObjectCache::frameLoadingEventPlatformNotification(AccessibilityObject*, AXLoadingEvent) { }
-inline void AXObjectCache::handleActiveDescendantChanged(Node*) { }
-inline void AXObjectCache::handleAriaExpandedChange(Node*) { }
-inline void AXObjectCache::handleAriaRoleChanged(Node*) { }
-inline void AXObjectCache::handleAriaModalChange(Node*) { }
+inline void AXObjectCache::handleActiveDescendantChanged(Element&) { }
+inline void AXObjectCache::handleAriaExpandedChange(Element&) { }
+inline void AXObjectCache::handleAriaRoleChanged(Element&) { }
+inline void AXObjectCache::handleAriaModalChange(Element&) { }
 inline void AXObjectCache::handleFocusedUIElementChanged(Node*, Node*) { }
 inline void AXObjectCache::handleScrollbarUpdate(ScrollView*) { }
-inline void AXObjectCache::handleAttributeChanged(const QualifiedName&, Element*) { }
+inline void AXObjectCache::handleAttributeChanged(const QualifiedName&, Element&) { }
 inline void AXObjectCache::recomputeIsIgnored(RenderObject*) { }
 inline void AXObjectCache::recomputeDeferredIsIgnored(RenderBlock&) { }
 inline void AXObjectCache::deferTextChanged(RenderText&) { }
@@ -512,7 +513,7 @@
 inline void AXObjectCache::remove(Node*) { }
 inline void AXObjectCache::remove(Widget*) { }
 inline void AXObjectCache::selectedChildrenChanged(RenderObject*) { }
-inline void AXObjectCache::selectedChildrenChanged(Node*) { }
+inline void AXObjectCache::selectedChildrenChanged(Element&) { }
 inline void AXObjectCache::setIsSynchronizingSelection(bool) { }
 inline void AXObjectCache::setTextSelectionIntent(const AXTextStateChangeIntent&) { }
 inline RefPtr<Range> AXObjectCache::rangeForUnorderedCharacterOffsets(const CharacterOffset&, const CharacterOffset&) { return nullptr; }

Modified: trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp (216418 => 216419)


--- trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp	2017-05-08 15:26:38 UTC (rev 216418)
+++ trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp	2017-05-08 15:33:47 UTC (rev 216419)
@@ -2442,7 +2442,7 @@
 {
     // This object might be deleted under the call to the parentObject() method.
     auto protectedThis = makeRef(*this);
-    
+
     // Find if a parent of this object should handle aria-expanded changes.
     AccessibilityObject* containerParent = this->parentObject();
     while (containerParent) {

Modified: trunk/Source/WebCore/dom/Element.cpp (216418 => 216419)


--- trunk/Source/WebCore/dom/Element.cpp	2017-05-08 15:26:38 UTC (rev 216418)
+++ trunk/Source/WebCore/dom/Element.cpp	2017-05-08 15:33:47 UTC (rev 216419)
@@ -1343,7 +1343,7 @@
     invalidateNodeListAndCollectionCachesInAncestors(&name, this);
 
     if (AXObjectCache* cache = document().existingAXObjectCache())
-        cache->handleAttributeChanged(name, this);
+        cache->handleAttributeChanged(name, *this);
 }
 
 template <typename CharacterType>

Modified: trunk/Source/WebCore/html/HTMLInputElement.cpp (216418 => 216419)


--- trunk/Source/WebCore/html/HTMLInputElement.cpp	2017-05-08 15:26:38 UTC (rev 216418)
+++ trunk/Source/WebCore/html/HTMLInputElement.cpp	2017-05-08 15:33:47 UTC (rev 216419)
@@ -915,7 +915,7 @@
     // because of the way the code is structured.
     if (renderer()) {
         if (AXObjectCache* cache = renderer()->document().existingAXObjectCache())
-            cache->checkedStateChanged(this);
+            cache->checkedStateChanged(*this);
     }
 
     // Only send a change event for items in the document (avoid firing during
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to