Defer certain accessibility callbacks until after layout is finished.
authorzalan@apple.com <zalan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 16 Dec 2016 18:48:31 +0000 (18:48 +0000)
committerzalan@apple.com <zalan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 16 Dec 2016 18:48:31 +0000 (18:48 +0000)
https://bugs.webkit.org/show_bug.cgi?id=165861
rdar://problem/29646301

Reviewed by Chris Fleizach.

Source/WebCore:

Currently with certain AXObjectCache callbacks, we can end up in a layout while the render tree is being mutated.
This patch ensures that such callbacks are deferred until after tree mutation/layout is finished.

Test: accessibility/accessibility-crash-with-dynamic-inline-content.html

* accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::remove):
(WebCore::AXObjectCache::performDeferredIsIgnoredChange):
(WebCore::AXObjectCache::insertDeferredIsIgnoredChange):
* accessibility/AXObjectCache.h:
* page/FrameView.cpp:
(WebCore::FrameView::performPostLayoutTasks):
* rendering/RenderBlock.cpp:
(WebCore::RenderBlock::deleteLines):
* rendering/RenderBlockLineLayout.cpp:
(WebCore::RenderBlockFlow::createAndAppendRootInlineBox):

LayoutTests:

* accessibility/accessibility-crash-with-dynamic-inline-content-expected.txt: Added.
* accessibility/accessibility-crash-with-dynamic-inline-content.html: Added.

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

LayoutTests/ChangeLog
LayoutTests/accessibility/accessibility-crash-with-dynamic-inline-content-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/accessibility-crash-with-dynamic-inline-content.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/accessibility/AXObjectCache.cpp
Source/WebCore/accessibility/AXObjectCache.h
Source/WebCore/page/FrameView.cpp
Source/WebCore/rendering/RenderBlock.cpp
Source/WebCore/rendering/RenderBlockLineLayout.cpp

index 0dc55dd..ab62ea1 100644 (file)
@@ -1,3 +1,14 @@
+2016-12-16  Zalan Bujtas  <zalan@apple.com>
+
+        Defer certain accessibility callbacks until after layout is finished.
+        https://bugs.webkit.org/show_bug.cgi?id=165861
+        rdar://problem/29646301
+
+        Reviewed by Chris Fleizach.
+
+        * accessibility/accessibility-crash-with-dynamic-inline-content-expected.txt: Added.
+        * accessibility/accessibility-crash-with-dynamic-inline-content.html: Added.
+
 2016-12-16  Youenn Fablet  <youenn@apple.com>
 
         [Fetch API] Improve resource loading console logging
diff --git a/LayoutTests/accessibility/accessibility-crash-with-dynamic-inline-content-expected.txt b/LayoutTests/accessibility/accessibility-crash-with-dynamic-inline-content-expected.txt
new file mode 100644 (file)
index 0000000..eb7705e
--- /dev/null
@@ -0,0 +1,3 @@
+PASS if no crash or assert. foo
+foobar
+
diff --git a/LayoutTests/accessibility/accessibility-crash-with-dynamic-inline-content.html b/LayoutTests/accessibility/accessibility-crash-with-dynamic-inline-content.html
new file mode 100644 (file)
index 0000000..73129a5
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>This tests accessibility with dynamic inline content.</title>
+</head>
+<body>
+PASS if no crash or assert.
+<span id="ariafoo">foo</span><div aria-labeledby = "ariafoo">foobar<details id="details" open="true">
+<script>
+if (window.testRunner) {
+  testRunner.dumpAsText();
+  testRunner.waitUntilDone();
+}
+setTimeout(function() {
+  details.open = false;
+  testRunner.notifyDone();
+}, 0);
+</script>
index 91f6969..c9f7b9a 100644 (file)
@@ -1,3 +1,28 @@
+2016-12-16  Zalan Bujtas  <zalan@apple.com>
+
+        Defer certain accessibility callbacks until after layout is finished.
+        https://bugs.webkit.org/show_bug.cgi?id=165861
+        rdar://problem/29646301
+
+        Reviewed by Chris Fleizach.
+
+        Currently with certain AXObjectCache callbacks, we can end up in a layout while the render tree is being mutated.  
+        This patch ensures that such callbacks are deferred until after tree mutation/layout is finished.
+
+        Test: accessibility/accessibility-crash-with-dynamic-inline-content.html
+
+        * accessibility/AXObjectCache.cpp:
+        (WebCore::AXObjectCache::remove):
+        (WebCore::AXObjectCache::performDeferredIsIgnoredChange):
+        (WebCore::AXObjectCache::insertDeferredIsIgnoredChange):
+        * accessibility/AXObjectCache.h:
+        * page/FrameView.cpp:
+        (WebCore::FrameView::performPostLayoutTasks):
+        * rendering/RenderBlock.cpp:
+        (WebCore::RenderBlock::deleteLines):
+        * rendering/RenderBlockLineLayout.cpp:
+        (WebCore::RenderBlockFlow::createAndAppendRootInlineBox):
+
 2016-12-16  Ryan Haddad  <ryanhaddad@apple.com>
 
         Rebaseline bindings tests after r209897.
index d1e8205..57a279b 100644 (file)
@@ -708,6 +708,8 @@ void AXObjectCache::remove(RenderObject* renderer)
     AXID axID = m_renderObjectMapping.get(renderer);
     remove(axID);
     m_renderObjectMapping.remove(renderer);
+    if (is<RenderBlock>(*renderer))
+        m_deferredIsIgnoredChangeList.remove(downcast<RenderBlock>(renderer));
 }
 
 void AXObjectCache::remove(Node* node)
@@ -2618,6 +2620,18 @@ bool AXObjectCache::nodeIsTextControl(const Node* node)
     return axObject && axObject->isTextControl();
 }
     
+void AXObjectCache::performDeferredIsIgnoredChange()
+{
+    for (auto* renderer : m_deferredIsIgnoredChangeList)
+        recomputeIsIgnored(renderer);
+    m_deferredIsIgnoredChangeList.clear();
+}
+
+void AXObjectCache::recomputeDeferredIsIgnored(RenderBlock& renderer)
+{
+    m_deferredIsIgnoredChangeList.add(&renderer);
+}
+
 bool isNodeAriaVisible(Node* node)
 {
     if (!node)
index 433c9ff..9c6fc31 100644 (file)
@@ -43,6 +43,7 @@ class Document;
 class HTMLAreaElement;
 class Node;
 class Page;
+class RenderBlock;
 class RenderObject;
 class ScrollView;
 class VisiblePosition;
@@ -323,6 +324,8 @@ public:
 #if PLATFORM(MAC)
     static void setShouldRepostNotificationsForTests(bool value);
 #endif
+    void recomputeDeferredIsIgnored(RenderBlock& renderer);
+    void performDeferredIsIgnoredChange();
 
 protected:
     void postPlatformNotification(AccessibilityObject*, AXNotification);
@@ -421,6 +424,7 @@ private:
 
     AXTextStateChangeIntent m_textSelectionIntent;
     bool m_isSynchronizingSelection { false };
+    ListHashSet<RenderBlock*> m_deferredIsIgnoredChangeList;
 };
 
 class AXAttributeCacheEnabler
index ead8ca6..167429a 100644 (file)
@@ -3472,6 +3472,9 @@ void FrameView::performPostLayoutTasks()
     viewportContentsChanged();
 
     updateScrollSnapState();
+
+    if (AXObjectCache* cache = frame().document()->existingAXObjectCache())
+        cache->performDeferredIsIgnoredChange();
 }
 
 IntSize FrameView::sizeForResizeEvent() const
index c8cf83a..ee531a4 100644 (file)
@@ -671,7 +671,7 @@ static void getInlineRun(RenderObject* start, RenderObject* boundary,
 void RenderBlock::deleteLines()
 {
     if (AXObjectCache* cache = document().existingAXObjectCache())
-        cache->recomputeIsIgnored(this);
+        cache->recomputeDeferredIsIgnored(*this);
 }
 
 void RenderBlock::makeChildrenNonInline(RenderObject* insertionPoint)
index a01787f..2abb482 100644 (file)
@@ -130,7 +130,7 @@ RootInlineBox* RenderBlockFlow::createAndAppendRootInlineBox()
 
     if (UNLIKELY(AXObjectCache::accessibilityEnabled()) && firstRootBox() == rootBox) {
         if (AXObjectCache* cache = document().existingAXObjectCache())
-            cache->recomputeIsIgnored(this);
+            cache->recomputeDeferredIsIgnored(*this);
     }
 
     return rootBox;