Reland "[A11y] Avoid queuing up a deferred update for every attached node."
This is a reland of commit de68001bf80113d11ff879405c41070b01602902
Reverted in CL:531819. Reason for revert: bug 326386227, failing All/DumpAccessibilityTreeTest.AccessibilityTableHeadersRowRoleDynamic/blink
Fix for test: restore the code that updates roles of <th> cells when sibling cells change.
Original change's description:
> [A11y] Avoid queuing up a deferred update for every attached node.
>
> This is a performance improvement that does not affect behavior.
>
> In many cases when a node is attached, there is no need to queue up
> work for later. Only queue deferred processing when needed.
> Note that ChildrenChanged() is called in many cases where there
> was already an AXObject, but these are deduped via
> nodes_with_pending_children_changed_.
>
> Bug: 324445636
> Change-Id: I93ca7da709bfcee37545edb395c209fcc594e443
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5311098
> Reviewed-by: Chris Harrelson <chrishtr@chromium.org>
> Commit-Queue: Aaron Leventhal <aleventhal@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1263666}
Fixed: 324445636,326386227
Change-Id: I010c8abe71e8e103e762b15188c7262695047e93
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5317851
Reviewed-by: Chris Harrelson <chrishtr@chromium.org>
Commit-Queue: Aaron Leventhal <aleventhal@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1264243}
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index bc569b91..17008f8 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -1109,7 +1109,7 @@
AXObject* AXObjectCacheImpl::GetAXImageForMap(HTMLMapElement& map) {
// Find first child node of <map> that has an AXObject and return it's
// parent, which should be a native image.
- Node* child = LayoutTreeBuilderTraversal::FirstChild(map);
+ Node* child = ElementTraversal::FirstChild(map);
while (child) {
if (AXObject* ax_child = Get(child)) {
if (AXObject* ax_image = ax_child->CachedParentObject()) {
@@ -1123,7 +1123,7 @@
return ax_image;
}
}
- child = LayoutTreeBuilderTraversal::NextSibling(*child);
+ child = ElementTraversal::NextSibling(*child);
}
return nullptr;
}
@@ -2633,73 +2633,67 @@
RemoveSubtreeWithFlatTraversal(node);
return;
}
+ // Rare edge case: if an image is added, it could have changed the order of
+ // images with the same usemap in the document. Only the first image for a
+ // given <map> should have the <area> children. Therefore, get the current
+ // primary image before it's updated, and ensure its children are
+ // recalculated.
+ if (IsA<HTMLImageElement>(node)) {
+ if (HTMLMapElement* map = AXObject::GetMapForImage(node)) {
+ HTMLImageElement* primary_image_element = map->ImageElement();
+ if (node != primary_image_element) {
+ ChildrenChanged(primary_image_element);
+ } else if (AXObject* ax_previous_parent = GetAXImageForMap(*map)) {
+ if (ax_previous_parent->GetNode() != node) {
+ ChildrenChanged(ax_previous_parent);
+ ax_previous_parent->ClearChildren();
+ }
+ }
+ }
+ }
if (IsA<HTMLTableElement>(node) && !node->IsFinishedParsingChildren() &&
!node_to_parse_before_more_tree_updates_) {
// Tables must be fully parsed before building, because many of the
// computed properties require the entire table.
node_to_parse_before_more_tree_updates_ = node;
}
- }
-
- DeferTreeUpdate(TreeUpdateReason::kNodeIsAttached, node);
-}
-
-void AXObjectCacheImpl::NodeIsAttachedWithCleanLayout(Node* node) {
- if (!node || !node->isConnected()) {
- return;
+ // Check if a row or cell's table changed to or from a data table.
+ if (IsA<HTMLTableRowElement>(node) || IsA<HTMLTableCellElement>(node)) {
+ HTMLTableElement* table =
+ Traversal<HTMLTableElement>::FirstAncestor(*node);
+ if (table) {
+ // This will change the table's role if necessary. If that occurs, the
+ // subtree will be removed and recomputed as well, because the
+ // table row and cell roles depend on the table's role (layout vs data).
+ DeferTreeUpdate(
+ TreeUpdateReason::kRoleMaybeChangedFromAttachedTableDescendant,
+ table);
+ }
+ if (IsA<HTMLTableCellElement>(node)) {
+ DeferTreeUpdate(
+ TreeUpdateReason::kTableCellSiblingHeaderRolesMaybeChanged, node);
+ }
+ }
}
Element* element = DynamicTo<Element>(node);
-
-#if DCHECK_IS_ON()
- DCHECK(node->GetDocument().Lifecycle().GetState() >=
- DocumentLifecycle::kLayoutClean)
- << "Unclean document at lifecycle "
- << node->GetDocument().Lifecycle().ToString();
-#endif // DCHECK_IS_ON()
-
if (AccessibleNode::GetPropertyOrARIAAttributeValue(
element, AOMRelationProperty::kActiveDescendant)) {
- HandleActiveDescendantChangedWithCleanLayout(element);
+ DeferTreeUpdate(TreeUpdateReason::kActiveDescendantChanged, node);
}
AXObject* obj = Get(node);
- MaybeNewRelationTarget(*node, obj);
- // Rare edge case: if an image is added, it could have changed the order of
- // images with the same usemap in the document. Only the first image for a
- // given <map> should have the <area> children. Therefore, get the current
- // primary image before it's updated, and ensure its children are
- // recalculated.
- if (IsA<HTMLImageElement>(node)) {
- if (HTMLMapElement* map = AXObject::GetMapForImage(node)) {
- HTMLImageElement* primary_image_element = map->ImageElement();
- if (node != primary_image_element) {
- ChildrenChangedWithCleanLayout(Get(primary_image_element));
- } else if (AXObject* ax_previous_parent = GetAXImageForMap(*map)) {
- if (ax_previous_parent->GetNode() != node) {
- ChildrenChangedWithCleanLayout(ax_previous_parent->GetNode(),
- ax_previous_parent);
- ax_previous_parent->ClearChildren();
- }
- }
- }
+ if (!obj) {
+ // Nothing to do if there was not a previous object.
+ return;
}
- // Check if a row or cell's table changed to or from a data table.
- if (IsA<HTMLTableRowElement>(node) || IsA<HTMLTableCellElement>(node)) {
- Element* parent = node->parentElement();
- while (parent) {
- if (DynamicTo<HTMLTableElement>(parent)) {
- break;
- }
- parent = parent->parentElement();
- }
- if (parent) {
- UpdateTableRoleWithCleanLayout(parent);
- }
- TableCellRoleMaybeChanged(node);
- }
+ // Even if the node or parent are ignored, an ancestor may need to include
+ // descendants of the attached node, thus ChildrenChanged()
+ // must be called. It handles ignored logic, ensuring that the first ancestor
+ // that should have this as a child will be updated.
+ ChildrenChanged(obj->CachedParentObject());
}
// Note: do not call this when a child is becoming newly included, because
@@ -2885,7 +2879,9 @@
relation_cache_->UpdateRelatedTree(optional_node, obj);
}
- TableCellRoleMaybeChanged(optional_node);
+ if (IsA<HTMLTableCellElement>(optional_node)) {
+ TableCellSiblingHeaderRolesMaybeChangedWithCleanLayout(optional_node);
+ }
}
void AXObjectCacheImpl::UpdateTreeIfNeeded() {
@@ -3703,6 +3699,7 @@
break;
case TreeUpdateReason::kRoleMaybeChangedFromEventListener:
case TreeUpdateReason::kRoleMaybeChangedFromHref:
+ case TreeUpdateReason::kRoleMaybeChangedFromAttachedTableDescendant:
HandleRoleMaybeChangedWithCleanLayout(node);
break;
case TreeUpdateReason::kSectionOrRegionRoleMaybeChangedFromLabel:
@@ -3710,6 +3707,9 @@
case TreeUpdateReason::kSectionOrRegionRoleMaybeChangedFromTitle:
SectionOrRegionRoleMaybeChangedWithCleanLayout(node);
break;
+ case TreeUpdateReason::kTableCellSiblingHeaderRolesMaybeChanged:
+ TableCellSiblingHeaderRolesMaybeChangedWithCleanLayout(node);
+ break;
case TreeUpdateReason::kTextChangedOnNode:
case TreeUpdateReason::kTextChangedOnClosestNodeForLayoutObject:
TextChangedWithCleanLayout(node);
@@ -3720,9 +3720,6 @@
case TreeUpdateReason::kUpdateActiveMenuOption:
HandleUpdateActiveMenuOptionWithCleanLayout(node);
break;
- case TreeUpdateReason::kNodeIsAttached:
- NodeIsAttachedWithCleanLayout(node);
- break;
case TreeUpdateReason::kUpdateAriaOwns:
UpdateAriaOwnsWithCleanLayout(node);
break;
@@ -4054,29 +4051,23 @@
HandleRoleMaybeChangedWithCleanLayout(element);
}
-void AXObjectCacheImpl::TableCellRoleMaybeChanged(Node* node) {
- if (!node) {
- return;
- }
- // The role for a table cell depends in complex ways on multiple of its
- // siblings (see DecideRoleFromSiblings). Rather than attempt to reproduce
- // that logic here for invalidation, just recompute the role of all siblings
- // when new table cells are added.
- if (auto* cell = DynamicTo<HTMLTableCellElement>(node)) {
- for (auto* prev = LayoutTreeBuilderTraversal::PreviousSibling(*cell); prev;
- prev = LayoutTreeBuilderTraversal::PreviousSibling(*prev)) {
- HandleRoleMaybeChangedWithCleanLayout(prev);
- }
- HandleRoleMaybeChangedWithCleanLayout(cell);
- for (auto* next = LayoutTreeBuilderTraversal::NextSibling(*cell); next;
- next = LayoutTreeBuilderTraversal::PreviousSibling(*next)) {
- HandleRoleMaybeChangedWithCleanLayout(next);
+void AXObjectCacheImpl::TableCellSiblingHeaderRolesMaybeChangedWithCleanLayout(
+ Node* node) {
+ // The role for a table header (<th>) depends in complex ways on multiple of
+ // its siblings (see DecideRoleFromSiblings). Rather than attempt to reproduce
+ // that logic here for invalidation, just recompute the role of all <th>
+ // siblings when new table cells are added.
+ DCHECK(IsA<HTMLTableCellElement>(node));
+ for (HTMLTableCellElement& cell :
+ Traversal<HTMLTableCellElement>::ChildrenOf(*node->parentNode())) {
+ if (cell.HasTagName(html_names::kThTag)) {
+ HandleRoleMaybeChangedWithCleanLayout(&cell);
}
}
}
void AXObjectCacheImpl::HandleRoleMaybeChangedWithCleanLayout(Node* node) {
- if (AXObject* obj = GetOrCreate(node)) {
+ if (AXObject* obj = Get(node)) {
// If role would stay the same, do nothing.
if (obj->RoleValue() == obj->DetermineAccessibilityRole()) {
return;
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
index b5b7f29..53531d5e 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
@@ -401,7 +401,7 @@
void HandleActiveDescendantChangedWithCleanLayout(Node*);
void SectionOrRegionRoleMaybeChangedWithCleanLayout(Node*);
- void TableCellRoleMaybeChanged(Node* node);
+ void TableCellSiblingHeaderRolesMaybeChangedWithCleanLayout(Node* node);
void HandleRoleMaybeChangedWithCleanLayout(Node*);
void HandleRoleChangeWithCleanLayout(Node*);
void HandleAriaExpandedChangeWithCleanLayout(Node*);
@@ -409,7 +409,6 @@
void HandleAriaPressedChangedWithCleanLayout(Node*);
void HandleNodeLostFocusWithCleanLayout(Node*);
void HandleNodeGainedFocusWithCleanLayout(Node*);
- void NodeIsAttachedWithCleanLayout(Node*);
void DidShowMenuListPopupWithCleanLayout(Node*);
void DidHideMenuListPopupWithCleanLayout(Node*);
void HandleScrollPositionChangedWithCleanLayout(Node*);
@@ -774,20 +773,21 @@
kRoleChangeFromAriaHasPopup = 20,
kRoleChangeFromImageMapName = 21,
kRoleChangeFromRoleOrType = 22,
- kRoleMaybeChangedFromEventListener = 23,
- kRoleMaybeChangedFromHref = 24,
- kSectionOrRegionRoleMaybeChangedFromLabel = 25,
- kSectionOrRegionRoleMaybeChangedFromLabelledBy = 26,
- kSectionOrRegionRoleMaybeChangedFromTitle = 27,
- kTextChangedOnNode = 28,
- kTextChangedOnClosestNodeForLayoutObject = 29,
- kTextMarkerDataAdded = 30,
- kUpdateActiveMenuOption = 31,
- kNodeIsAttached = 32,
- kUpdateAriaOwns = 33,
- kUpdateTableRole = 34,
- kUseMapAttributeChanged = 35,
- kValidationMessageVisibilityChanged = 36,
+ kRoleMaybeChangedFromAttachedTableDescendant = 23,
+ kRoleMaybeChangedFromEventListener = 24,
+ kRoleMaybeChangedFromHref = 25,
+ kSectionOrRegionRoleMaybeChangedFromLabel = 26,
+ kSectionOrRegionRoleMaybeChangedFromLabelledBy = 27,
+ kSectionOrRegionRoleMaybeChangedFromTitle = 28,
+ kTableCellSiblingHeaderRolesMaybeChanged = 29,
+ kTextChangedOnNode = 30,
+ kTextChangedOnClosestNodeForLayoutObject = 31,
+ kTextMarkerDataAdded = 32,
+ kUpdateActiveMenuOption = 33,
+ kUpdateAriaOwns = 34,
+ kUpdateTableRole = 35,
+ kUseMapAttributeChanged = 36,
+ kValidationMessageVisibilityChanged = 37,
// These updates are associated with an AXID:
kChildrenChanged = 100,