Skip to content

Commit

Permalink
Merge r228417 - AX: defer focusedUIElement notifications
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=182643
<rdar://problem/37394310>

Reviewed by Zalan Bujtas.

Source/WebCore:

Deferring focus changes for accessibility has a number of benefits.
    1) Reduces the chance of calling into layout during layout.
    2) Coalesces multiple focus notifications that would be needlessly sent.
    3) Improves performance by not calling out to the accessibility notification machinery during layout.

In this patch, I also started making more AXObjectCache calls private. This will reduce the chance that clients
will call into AXObjectCache during unexpected times.

* accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::deferFocusedUIElementChangeIfNeeded):
(WebCore::conditionallyAddNodeToFilterList):
(WebCore::filterVectorPairForRemoval):
(WebCore::filterMapForRemoval):
(WebCore::filterListForRemoval):
(WebCore::AXObjectCache::prepareForDocumentDestruction):
(WebCore::AXObjectCache::performDeferredCacheUpdate):
* accessibility/AXObjectCache.h:
* dom/Document.cpp:
(WebCore::Document::setFocusedElement):

LayoutTests:

* accessibility/mac/aria-menu-item-selected-notification.html:
     Rewrite test to accomodate that focus changes happen asynchronously.
* accessibility/mac/selection-notification-focus-change-expected.txt:
* platform/mac-wk2/accessibility/mac/selection-notification-focus-change-expected.txt:
     The order of notifications is different now that focus changes happen later.
  • Loading branch information
fleizach authored and carlosgcampos committed Feb 20, 2018
1 parent f8e9930 commit f8fab14
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 31 deletions.
14 changes: 14 additions & 0 deletions LayoutTests/ChangeLog
@@ -1,3 +1,17 @@
2018-02-12 Chris Fleizach <cfleizach@apple.com>

AX: defer focusedUIElement notifications
https://bugs.webkit.org/show_bug.cgi?id=182643
<rdar://problem/37394310>

Reviewed by Zalan Bujtas.

* accessibility/mac/aria-menu-item-selected-notification.html:
Rewrite test to accomodate that focus changes happen asynchronously.
* accessibility/mac/selection-notification-focus-change-expected.txt:
* platform/mac-wk2/accessibility/mac/selection-notification-focus-change-expected.txt:
The order of notifications is different now that focus changes happen later.

2018-02-12 John Wilander <wilander@apple.com>

Resource Load Statistics: Classify resources as prevalent based on redirects to other prevalent resources
Expand Down
Expand Up @@ -46,14 +46,20 @@
// Trigger notification through focus.
document.getElementById("item1").focus();

// Trigger notification through aria-selected.
document.getElementById("item2").setAttribute("aria-selected", "true");
setTimeout(function() {
// Trigger notification through aria-selected.
document.getElementById("item2").setAttribute("aria-selected", "true");

// Ensure we don't get a notification when aria-selected is false.
document.getElementById("item2").setAttribute("aria-selected", "false");
setTimeout(function() {
// Ensure we don't get a notification when aria-selected is false.
document.getElementById("item2").setAttribute("aria-selected", "false");

// Trigger another notification through focus to ensure we don't
document.getElementById("item3").focus();
setTimeout(function() {
// Trigger another notification through focus to ensure we don't
document.getElementById("item3").focus();
}, 1);
}, 1);
}, 1);
}

</script>
Expand Down
Expand Up @@ -16,9 +16,9 @@ accessibilityController.accessibleElementById("1").takeFocus()
Received AXFocusChanged

eventSender.keyDown(tabCharacter)
Received AXFocusChanged
Received AXSelectedTextChanged
PASS userInfo["AXTextSelectionChangedFocus"] is true
Received AXFocusChanged
Received AXSelectedTextChanged
PASS userInfo["AXTextSelectionChangedFocus"] is true
PASS successfullyParsed is true
Expand Down
Expand Up @@ -7,18 +7,18 @@ PASS webArea.addNotificationListener(notificationCallback) is true
eventSender.keyDown(tabCharacter);
Received AXSelectedTextChanged
PASS userInfo["AXTextSelectionChangedFocus"] is true
Received AXFocusChanged
Received AXSelectedTextChanged
PASS userInfo["AXTextSelectionChangedFocus"] is true
Received AXFocusChanged

PASS accessibilityController.accessibleElementById("1").isFocusable is true
accessibilityController.accessibleElementById("1").takeFocus()
Received AXFocusChanged

eventSender.keyDown(tabCharacter)
Received AXFocusChanged
Received AXSelectedTextChanged
PASS userInfo["AXTextSelectionChangedFocus"] is true
Received AXFocusChanged
PASS successfullyParsed is true

TEST COMPLETE
Expand Down
28 changes: 28 additions & 0 deletions Source/WebCore/ChangeLog
@@ -1,3 +1,31 @@
2018-02-12 Chris Fleizach <cfleizach@apple.com>

AX: defer focusedUIElement notifications
https://bugs.webkit.org/show_bug.cgi?id=182643
<rdar://problem/37394310>

Reviewed by Zalan Bujtas.

Deferring focus changes for accessibility has a number of benefits.
1) Reduces the chance of calling into layout during layout.
2) Coalesces multiple focus notifications that would be needlessly sent.
3) Improves performance by not calling out to the accessibility notification machinery during layout.

In this patch, I also started making more AXObjectCache calls private. This will reduce the chance that clients
will call into AXObjectCache during unexpected times.

* accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::deferFocusedUIElementChangeIfNeeded):
(WebCore::conditionallyAddNodeToFilterList):
(WebCore::filterVectorPairForRemoval):
(WebCore::filterMapForRemoval):
(WebCore::filterListForRemoval):
(WebCore::AXObjectCache::prepareForDocumentDestruction):
(WebCore::AXObjectCache::performDeferredCacheUpdate):
* accessibility/AXObjectCache.h:
* dom/Document.cpp:
(WebCore::Document::setFocusedElement):

2018-02-12 John Wilander <wilander@apple.com>

Resource Load Statistics: Classify resources as prevalent based on redirects to other prevalent resources
Expand Down
43 changes: 32 additions & 11 deletions Source/WebCore/accessibility/AXObjectCache.cpp
Expand Up @@ -1017,6 +1017,14 @@ void AXObjectCache::handleMenuItemSelected(Node* node)
postNotification(getOrCreate(node), &document(), AXMenuListItemSelected);
}

void AXObjectCache::deferFocusedUIElementChangeIfNeeded(Node* oldNode, Node* newNode)
{
if (nodeAndRendererAreValid(newNode) && rendererNeedsDeferredUpdate(*newNode->renderer()))
m_deferredFocusedNodeChange.append({ oldNode, newNode });
else
handleFocusedUIElementChanged(oldNode, newNode);
}

void AXObjectCache::handleFocusedUIElementChanged(Node* oldNode, Node* newNode)
{
handleMenuItemSelected(newNode);
Expand Down Expand Up @@ -2777,25 +2785,33 @@ const Element* AXObjectCache::rootAXEditableElement(const Node* node)
return result;
}

template<typename T, typename U>
static void filterMapForRemoval(const HashMap<T, U>& list, const Document& document, HashSet<Node*>& nodesToRemove)
static void conditionallyAddNodeToFilterList(Node* node, const Document& document, HashSet<Node*>& nodesToRemove)
{
for (auto& entry : list) {
auto* node = entry.key;
if (node->isConnected() && &node->document() != &document)
continue;
if (node && (!node->isConnected() || &node->document() == &document))
nodesToRemove.add(node);
}

template<typename T>
static void filterVectorPairForRemoval(const Vector<std::pair<T, T>>& list, const Document& document, HashSet<Node*>& nodesToRemove)
{
for (auto& entry : list) {
conditionallyAddNodeToFilterList(entry.first, document, nodesToRemove);
conditionallyAddNodeToFilterList(entry.second, document, nodesToRemove);
}
}

template<typename T, typename U>
static void filterMapForRemoval(const HashMap<T, U>& list, const Document& document, HashSet<Node*>& nodesToRemove)
{
for (auto& entry : list)
conditionallyAddNodeToFilterList(entry.key, document, nodesToRemove);
}

template<typename T>
static void filterListForRemoval(const ListHashSet<T>& list, const Document& document, HashSet<Node*>& nodesToRemove)
{
for (auto* node : list) {
if (node->isConnected() && &node->document() != &document)
continue;
nodesToRemove.add(node);
}
for (auto* node : list)
conditionallyAddNodeToFilterList(node, document, nodesToRemove);
}

void AXObjectCache::prepareForDocumentDestruction(const Document& document)
Expand All @@ -2808,6 +2824,7 @@ void AXObjectCache::prepareForDocumentDestruction(const Document& document)
filterListForRemoval(m_deferredSelectedChildredChangedList, document, nodesToRemove);
filterMapForRemoval(m_deferredTextFormControlValue, document, nodesToRemove);
filterMapForRemoval(m_deferredAttributeChange, document, nodesToRemove);
filterVectorPairForRemoval(m_deferredFocusedNodeChange, document, nodesToRemove);

for (auto* node : nodesToRemove)
remove(*node);
Expand Down Expand Up @@ -2851,6 +2868,10 @@ void AXObjectCache::performDeferredCacheUpdate()
for (auto& deferredAttributeChangeContext : m_deferredAttributeChange)
handleAttributeChange(deferredAttributeChangeContext.value, deferredAttributeChangeContext.key);
m_deferredAttributeChange.clear();

for (auto& deferredFocusedChangeContext : m_deferredFocusedNodeChange)
handleFocusedUIElementChanged(deferredFocusedChangeContext.first, deferredFocusedChangeContext.second);
m_deferredFocusedNodeChange.clear();
}

void AXObjectCache::deferRecomputeIsIgnoredIfNeeded(Element* element)
Expand Down
22 changes: 12 additions & 10 deletions Source/WebCore/accessibility/AXObjectCache.h
Expand Up @@ -168,21 +168,13 @@ class AXObjectCache {
void childrenChanged(RenderObject*, RenderObject* newChild = nullptr);
void childrenChanged(AccessibilityObject*);
void checkedStateChanged(Node*);
void selectedChildrenChanged(Node*);
void selectedChildrenChanged(RenderObject*);
// Called by a node when text or a text equivalent (e.g. alt) attribute is changed.
void textChanged(Node*);
// 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 deferFocusedUIElementChangeIfNeeded(Node* oldFocusedNode, Node* newFocusedNode);
void handleScrolledToAnchor(const Node* anchorNode);
void handleAriaExpandedChange(Node*);
void handleScrollbarUpdate(ScrollView*);

void handleModalChange(Node*);
Node* modalNode();

void deferAttributeChangeIfNeeded(const QualifiedName&, Element*);
Expand Down Expand Up @@ -411,11 +403,20 @@ class AXObjectCache {
void handleMenuItemSelected(Node*);
void handleAttributeChange(const QualifiedName&, Element*);
bool shouldProcessAttributeChange(const QualifiedName&, Element*);

void selectedChildrenChanged(Node*);
void selectedChildrenChanged(RenderObject*);
// Called by a node when text or a text equivalent (e.g. alt) attribute is changed.
void textChanged(Node*);
void handleActiveDescendantChanged(Node*);
void handleAriaRoleChanged(Node*);
void handleAriaExpandedChange(Node*);
void handleFocusedUIElementChanged(Node* oldFocusedNode, Node* newFocusedNode);

// aria-modal related
void findModalNodes();
void updateCurrentModalNode();
bool isNodeVisible(Node*) const;
void handleModalChange(Node*);

Document& m_document;
HashMap<AXID, RefPtr<AccessibilityObject>> m_objects;
Expand Down Expand Up @@ -449,6 +450,7 @@ class AXObjectCache {
ListHashSet<Element*> m_deferredSelectedChildredChangedList;
HashMap<Element*, String> m_deferredTextFormControlValue;
HashMap<Element*, QualifiedName> m_deferredAttributeChange;
Vector<std::pair<Node*, Node*>> m_deferredFocusedNodeChange;
bool m_isSynchronizingSelection { false };
bool m_performingDeferredCacheUpdate { false };
};
Expand Down
2 changes: 1 addition & 1 deletion Source/WebCore/dom/Document.cpp
Expand Up @@ -3972,7 +3972,7 @@ bool Document::setFocusedElement(Element* element, FocusDirection direction, Foc
if (!focusChangeBlocked && m_focusedElement) {
// Create the AXObject cache in a focus change because GTK relies on it.
if (AXObjectCache* cache = axObjectCache())
cache->handleFocusedUIElementChanged(oldFocusedElement.get(), newFocusedElement.get());
cache->deferFocusedUIElementChangeIfNeeded(oldFocusedElement.get(), newFocusedElement.get());
}

if (!focusChangeBlocked && page())
Expand Down

0 comments on commit f8fab14

Please sign in to comment.