0

Revert set of a11y tree commits which cause crashes

It is not practical to revert each CL individually, so I have
created this CL to bundle all the reverts together.

da5d2ffb6d
4463d9a99f
2278ce249c
97778ef778
27716a230a
76b1d09313
faed33fe90
14c61afb23
962d5db870
b18739d115
4066dd6c1c

crrev.com/c/3964415
crrev.com/c/3970603
crrev.com/c/3974832
crrev.com/c/3976209
crrev.com/c/3971173
crrev.com/c/3972090
crrev.com/c/3970627
crrev.com/c/3970885
crrev.com/c/3976391
crrev.com/c/3978892
crrev.com/c/3975164

Revert "Move a11y to post-lifecycle steps (iteration )"

This reverts commit da5d2ffb6d.


Revert "AXObjectCache is still dirty if tree updates are paused"

This reverts commit 4463d9a99f.


Revert "Remove extra call to ProcessDeferredAccessibilityEvents()"

This reverts commit 2278ce249c.


Revert "More robust handling of aria-activedescendant invalidations"

This reverts commit 97778ef778.


Revert "Avoid redundant calls to UpdateAXForAllDocuments()"

This reverts commit 27716a230a.


Revert "Restore popup guard"

This reverts commit 76b1d09313.


Revert "Use DCHECKs to prevent recursive calls in AXObjectCacheImpl"

This reverts commit faed33fe90.


Revert "Avoid raw pointer in WebAXObjectProxyList"

This reverts commit 14c61afb23.


Revert "Clean up load logic so that it's easier to understand"

This reverts commit 962d5db870.


Revert "Deflake web tests that create the root ax object"

This reverts commit b18739d115.


Revert "Do not create orphaned AXObjects for whitespace text"

This reverts commit 4066dd6c1c.

Bug: 1376991
Change-Id: I9869417a9b9333fdf3a6b5796f94eabf9ac168e4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3990067
Commit-Queue: Joel Hockey <joelhockey@chromium.org>
Owners-Override: Leo Zhang <googleo@google.com>
Reviewed-by: Colin Kincaid <ckincaid@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1064718}
This commit is contained in:
Joel Hockey
2022-10-28 06:09:16 +00:00
committed by Chromium LUCI CQ
parent 003b9cc457
commit 3c7eb577ee
21 changed files with 425 additions and 232 deletions

@ -257,7 +257,6 @@ void RenderAccessibilityImpl::AccessibilityModeChanged(const ui::AXMode& mode) {
if (settings) {
if (mode.has_mode(ui::AXMode::kInlineTextBoxes)) {
settings->SetInlineTextBoxAccessibilityEnabled(true);
ax_context_->UpdateAXForAllDocuments();
ComputeRoot().LoadInlineTextBoxes();
} else {
settings->SetInlineTextBoxAccessibilityEnabled(false);
@ -372,7 +371,7 @@ void RenderAccessibilityImpl::HitTest(
}
void RenderAccessibilityImpl::PerformAction(const ui::AXActionData& data) {
WebDocument document = GetMainDocument();
const WebDocument& document = GetMainDocument();
if (document.IsNull())
return;
@ -1148,11 +1147,16 @@ void RenderAccessibilityImpl::SendPendingAccessibilityEvents() {
#if DCHECK_IS_ON()
// Protect against lifecycle changes in the popup document, if any.
// If no popup document, use the main document -- it's harmless to protect it
// twice, and some document is needed because this cannot be done in an if
// statement because it's scoped.
WebDocument popup_document = GetPopupDocument();
WebDocument popup_or_main_document =
popup_document.IsNull() ? document : GetPopupDocument();
std::unique_ptr<blink::WebDisallowTransitionScope> disallow2;
if (!image_annotation_debugging_ && !popup_document.IsNull()) {
disallow2 =
std::make_unique<blink::WebDisallowTransitionScope>(&popup_document);
if (!image_annotation_debugging_) {
disallow = std::make_unique<blink::WebDisallowTransitionScope>(
&popup_or_main_document);
}
#endif

@ -465,6 +465,170 @@ TEST_F(RenderAccessibilityImplTest, SendFullAccessibilityTreeOnReload) {
EXPECT_EQ(6, CountAccessibilityNodesSentToBrowser());
}
TEST_F(RenderAccessibilityImplTest, TestDeferred) {
constexpr char html[] = R"HTML(
<body>
<div>
a
</div>
</body>
)HTML";
LoadHTML(html);
task_environment_.RunUntilIdle();
// We should have had load complete. Subsequent events are deferred unless
// there is a user interaction.
ExpectScheduleStatusScheduledDeferred();
ExpectScheduleModeDeferEvents();
// Simulate a page load to test deferred behavior.
GetRenderAccessibilityImpl()->DidCommitProvisionalLoad(
ui::PageTransition::PAGE_TRANSITION_LINK);
ClearHandledUpdates();
WebDocument document = GetMainFrame()->GetDocument();
EXPECT_FALSE(document.IsNull());
WebAXObject root_obj = WebAXObject::FromWebDocument(document);
EXPECT_FALSE(root_obj.IsNull());
// No events should have been scheduled or sent.
ExpectScheduleStatusNotWaiting();
ExpectScheduleModeDeferEvents();
// Send a non-interactive event, it should be scheduled with a delay.
GetRenderAccessibilityImpl()->HandleAXEvent(
ui::AXEvent(root_obj.AxID(), ax::mojom::Event::kLocationChanged));
ExpectScheduleStatusScheduledDeferred();
ExpectScheduleModeDeferEvents();
task_environment_.RunUntilIdle();
// Ensure event is not sent as it is scheduled with a delay.
ExpectScheduleStatusScheduledDeferred();
ExpectScheduleModeDeferEvents();
// Perform action, causing immediate event processing.
ui::AXActionData action;
action.action = ax::mojom::Action::kFocus;
GetRenderAccessibilityImpl()->PerformAction(action);
ScheduleSendPendingAccessibilityEvents();
// Once in immediate mode, stays in immediate mode until events are sent.
GetRenderAccessibilityImpl()->HandleAXEvent(
ui::AXEvent(root_obj.AxID(), ax::mojom::Event::kLocationChanged));
ExpectScheduleStatusScheduledImmediate();
ExpectScheduleModeProcessEventsImmediately();
// Once events have been sent, defer next batch.
ScheduleSendPendingAccessibilityEvents();
task_environment_.RunUntilIdle();
ExpectScheduleStatusScheduledDeferred();
ExpectScheduleModeDeferEvents();
const std::vector<ax::mojom::Event> kNonInteractiveEvents = {
ax::mojom::Event::kAriaAttributeChanged,
ax::mojom::Event::kChildrenChanged,
ax::mojom::Event::kDocumentTitleChanged,
ax::mojom::Event::kExpandedChanged,
ax::mojom::Event::kHide,
ax::mojom::Event::kLayoutComplete,
ax::mojom::Event::kLocationChanged,
ax::mojom::Event::kMenuListValueChanged,
ax::mojom::Event::kRowCollapsed,
ax::mojom::Event::kRowCountChanged,
ax::mojom::Event::kRowExpanded,
ax::mojom::Event::kScrollPositionChanged,
ax::mojom::Event::kScrolledToAnchor,
ax::mojom::Event::kSelectedChildrenChanged,
ax::mojom::Event::kShow,
ax::mojom::Event::kTextChanged};
for (ax::mojom::Event event : kNonInteractiveEvents) {
// Send an interactive event, it should be scheduled with a delay.
GetRenderAccessibilityImpl()->HandleAXEvent(
ui::AXEvent(root_obj.AxID(), event));
ExpectScheduleModeDeferEvents();
}
ScheduleSendPendingAccessibilityEvents();
ExpectScheduleStatusScheduledDeferred();
const std::vector<ax::mojom::Event> kInteractiveEvents = {
ax::mojom::Event::kActiveDescendantChanged,
ax::mojom::Event::kBlur,
ax::mojom::Event::kCheckedStateChanged,
ax::mojom::Event::kClicked,
ax::mojom::Event::kDocumentSelectionChanged,
ax::mojom::Event::kFocus,
ax::mojom::Event::kHover,
ax::mojom::Event::kLoadComplete,
ax::mojom::Event::kValueChanged};
for (ax::mojom::Event event : kInteractiveEvents) {
// Once events have been sent, defer next batch.
task_environment_.RunUntilIdle();
ExpectScheduleModeDeferEvents();
ExpectScheduleStatusScheduledDeferred();
// Send an interactive event, it should be scheduled with a delay.
GetRenderAccessibilityImpl()->HandleAXEvent(
ui::AXEvent(root_obj.AxID(), event));
ExpectScheduleModeProcessEventsImmediately();
ExpectScheduleStatusScheduledImmediate();
ScheduleSendPendingAccessibilityEvents();
}
task_environment_.RunUntilIdle();
// Event has been sent, no longer waiting on ack.
ExpectScheduleStatusScheduledDeferred();
ExpectScheduleModeDeferEvents();
}
TEST_F(RenderAccessibilityImplTest, TestChangesOnFocusModeAreImmediate) {
LoadHTML(R"HTML(
<body>
<div id=a tabindex=0>
a
</div>
<script>document.getElementById('a').focus();</script>
</body>
)HTML");
task_environment_.RunUntilIdle();
// We should have had load complete. Subsequent events are deferred unless
// there is a user interaction.
ExpectScheduleStatusScheduledDeferred();
ExpectScheduleModeDeferEvents();
// Simulate a page load to test deferred behavior.
GetRenderAccessibilityImpl()->DidCommitProvisionalLoad(
ui::PageTransition::PAGE_TRANSITION_LINK);
ClearHandledUpdates();
WebDocument document = GetMainFrame()->GetDocument();
EXPECT_FALSE(document.IsNull());
WebAXObject root_obj = WebAXObject::FromWebDocument(document);
EXPECT_FALSE(root_obj.IsNull());
WebAXObject html = root_obj.ChildAt(0);
WebAXObject body = html.ChildAt(0);
WebAXObject node_a = body.ChildAt(0);
// No events should have been scheduled or sent.
ExpectScheduleStatusNotWaiting();
ExpectScheduleModeDeferEvents();
// Marking the focused object dirty causes changes to be sent immediately.
GetRenderAccessibilityImpl()->MarkWebAXObjectDirty(node_a, false);
ExpectScheduleStatusScheduledImmediate();
ExpectScheduleModeProcessEventsImmediately();
task_environment_.RunUntilIdle();
// Event has been sent, no longer waiting on ack.
ExpectScheduleStatusScheduledDeferred();
ExpectScheduleModeDeferEvents();
}
TEST_F(RenderAccessibilityImplTest, HideAccessibilityObject) {
// Test RenderAccessibilityImpl and make sure it sends the
// proper event to the browser when an object in the tree

@ -1,5 +1,5 @@
EVENT_OBJECT_HIDE on <div.a> role=ROLE_SYSTEM_GROUPING name="Heading" level=2
EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL
EVENT_OBJECT_SHOW on <div.b> role=ROLE_SYSTEM_GROUPING name="Banner"
IA2_EVENT_TEXT_INSERTED on <div> role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL new_text={'<obj>' start=1 end=2}
IA2_EVENT_TEXT_REMOVED on <div> role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL old_text={'<obj>' start=0 end=1}
IA2_EVENT_TEXT_INSERTED on <div> role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL new_text={'<obj>' start=0 end=1}
IA2_EVENT_TEXT_REMOVED on <div> role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL old_text={'<obj>' start=0 end=1}

@ -163,9 +163,7 @@ AccessibilityController::~AccessibilityController() {
}
void AccessibilityController::Reset() {
if (!IsInstalled())
return;
elements_->Clear();
elements_.Clear();
notification_callback_.Reset();
log_accessibility_events_ = false;
ax_context_.reset();
@ -174,7 +172,7 @@ void AccessibilityController::Reset() {
void AccessibilityController::Install(blink::WebLocalFrame* frame) {
ax_context_ = std::make_unique<blink::WebAXContext>(frame->GetDocument(),
ui::kAXModeComplete);
elements_ = std::make_unique<WebAXObjectProxyList>(*ax_context_);
elements_.SetAXContext(ax_context_.get());
frame->View()->GetSettings()->SetInlineTextBoxAccessibilityEnabled(true);
AccessibilityControllerBindings::Install(weak_factory_.GetWeakPtr(), frame);
@ -200,9 +198,6 @@ void AccessibilityController::PostNotification(
const blink::WebAXObject& target,
const std::string& notification_name,
const std::vector<ui::AXEventIntent>& event_intents) {
if (!IsInstalled())
return;
v8::Isolate* isolate = blink::MainThreadIsolate();
v8::HandleScope handle_scope(isolate);
@ -218,7 +213,7 @@ void AccessibilityController::PostNotification(
v8::Context::Scope context_scope(context);
// Call notification listeners on the element.
v8::Local<v8::Object> element_handle = elements_->GetOrCreate(target);
v8::Local<v8::Object> element_handle = elements_.GetOrCreate(target);
if (element_handle.IsEmpty())
return;
@ -259,7 +254,7 @@ void AccessibilityController::UnsetNotificationListener() {
v8::Local<v8::Object> AccessibilityController::FocusedElement() {
blink::WebFrame* frame = web_view()->MainFrame();
if (!frame || !IsInstalled())
if (!frame)
return v8::Local<v8::Object>();
// TODO(lukasza): Finish adding OOPIF support to the web tests harness.
@ -272,14 +267,11 @@ v8::Local<v8::Object> AccessibilityController::FocusedElement() {
frame->ToWebLocalFrame()->GetDocument());
if (focused_element.IsNull())
focused_element = GetAccessibilityObjectForMainFrame();
return elements_->GetOrCreate(focused_element);
return elements_.GetOrCreate(focused_element);
}
v8::Local<v8::Object> AccessibilityController::RootElement() {
if (!IsInstalled())
return v8::Local<v8::Object>();
ax_context_->UpdateAXForAllDocuments();
return elements_->GetOrCreate(GetAccessibilityObjectForMainFrame());
return elements_.GetOrCreate(GetAccessibilityObjectForMainFrame());
}
v8::Local<v8::Object> AccessibilityController::AccessibleElementById(
@ -307,7 +299,7 @@ AccessibilityController::FindAccessibleElementByIdRecursive(
if (!node.IsNull() && node.IsElementNode()) {
blink::WebElement element = node.To<blink::WebElement>();
if (element.GetAttribute("id") == id)
return elements_->GetOrCreate(obj);
return elements_.GetOrCreate(obj);
}
unsigned childCount = obj.ChildCount();

@ -61,7 +61,6 @@ class AccessibilityController {
v8::Local<v8::Object> RootElement();
v8::Local<v8::Object> AccessibleElementById(const std::string& id);
bool CanCallAOMEventListeners() const;
bool IsInstalled() { return elements_ != nullptr; }
v8::Local<v8::Object> FindAccessibleElementByIdRecursive(
const blink::WebAXObject&,
@ -72,7 +71,7 @@ class AccessibilityController {
// If true, will log all accessibility notifications.
bool log_accessibility_events_;
std::unique_ptr<WebAXObjectProxyList> elements_;
WebAXObjectProxyList elements_;
v8::Persistent<v8::Function> notification_callback_;

@ -1771,8 +1771,7 @@ bool RootWebAXObjectProxy::IsRoot() const {
return true;
}
WebAXObjectProxyList::WebAXObjectProxyList(blink::WebAXContext& ax_context)
: ax_context_(&ax_context) {}
WebAXObjectProxyList::WebAXObjectProxyList() = default;
WebAXObjectProxyList::~WebAXObjectProxyList() {
Clear();
@ -1832,6 +1831,10 @@ v8::Local<v8::Object> WebAXObjectProxyList::GetOrCreate(
return handle;
}
void WebAXObjectProxyList::SetAXContext(blink::WebAXContext* ax_context) {
ax_context_ = ax_context;
}
blink::WebAXContext* WebAXObjectProxyList::GetAXContext() {
return ax_context_;
}

@ -30,6 +30,7 @@ class WebAXObjectProxy : public gin::Wrappable<WebAXObjectProxy> {
virtual ~Factory() {}
virtual v8::Local<v8::Object> GetOrCreate(
const blink::WebAXObject& object) = 0;
virtual void SetAXContext(blink::WebAXContext* ax_context) = 0;
virtual blink::WebAXContext* GetAXContext() = 0;
};
@ -256,18 +257,30 @@ class RootWebAXObjectProxy : public WebAXObjectProxy {
bool IsRoot() const override;
};
// Provides simple lifetime management of the WebAXObjectProxy instances: all
// WebAXObjectProxys ever created from the controller are stored in a list and
// cleared explicitly.
// TODO(aleventhal) Don't store ax_context_ as a non-const raw pointer.
// Refactor as the following:
// - The constructor takes a blink::WebAXContext& argument.
// - ax_context_ is declared as 'blink::WebAXContext* const ax_context_;'
// - Get rid of SetAXContext()
// - std::unique_ptr<WebAXObjectProxyList> AccessibilityController::elements_;
// - AccessibilityController::elements_ is populated from
// AccessibilityController::Install().
class WebAXObjectProxyList : public WebAXObjectProxy::Factory {
public:
explicit WebAXObjectProxyList(blink::WebAXContext&);
WebAXObjectProxyList();
~WebAXObjectProxyList() override;
void Clear();
v8::Local<v8::Object> GetOrCreate(const blink::WebAXObject&) override;
void SetAXContext(blink::WebAXContext* ax_context) override;
blink::WebAXContext* GetAXContext() override;
private:
std::vector<v8::Global<v8::Object>> elements_;
blink::WebAXContext* const ax_context_;
blink::WebAXContext* ax_context_ = nullptr;
};
} // namespace content

@ -3466,13 +3466,20 @@ DocumentParser* Document::ImplicitOpen(
}
void Document::DispatchHandleLoadStart() {
if (AXObjectCache* cache = ExistingAXObjectCache())
cache->HandleLoadStart(this);
if (AXObjectCache* cache = ExistingAXObjectCache()) {
// Don't fire load start for popup document.
if (this == &AXObjectCacheOwner())
cache->HandleLoadStart(this);
}
}
void Document::DispatchHandleLoadComplete() {
if (AXObjectCache* cache = ExistingAXObjectCache())
cache->HandleLoadComplete(this);
void Document::DispatchHandleLoadOrLayoutComplete() {
if (AXObjectCache* cache = ExistingAXObjectCache()) {
if (this == &AXObjectCacheOwner())
cache->HandleLoadComplete(this);
else
cache->HandleLayoutComplete(this);
}
}
HTMLElement* Document::body() const {
@ -3711,7 +3718,7 @@ void Document::ImplicitClose() {
load_event_progress_ = kLoadEventCompleted;
if (GetFrame() && GetLayoutView()) {
DispatchHandleLoadComplete();
DispatchHandleLoadOrLayoutComplete();
FontFaceSetDocument::DidLayout(*this);
}

@ -1766,7 +1766,7 @@ class CORE_EXPORT Document : public ContainerNode,
bool IsAccessibilityEnabled() const { return !ax_contexts_.empty(); }
void DispatchHandleLoadStart();
void DispatchHandleLoadComplete();
void DispatchHandleLoadOrLayoutComplete();
bool HaveRenderBlockingStylesheetsLoaded() const;
bool HaveRenderBlockingResourcesLoaded() const;

@ -128,6 +128,19 @@ bool DocumentLifecycle::CanAdvanceTo(LifecycleState next_state) const {
return true;
if (next_state == kStyleClean)
return true;
// InAccessibility only runs if there is an ExistingAXObjectCache.
if (next_state == kInAccessibility)
return true;
if (next_state == kInCompositingInputsUpdate)
return true;
if (next_state == kInPrePaint)
return true;
break;
case kInAccessibility:
if (next_state == kAccessibilityClean)
return true;
break;
case kAccessibilityClean:
if (next_state == kInCompositingInputsUpdate)
return true;
if (next_state == kInPrePaint)
@ -143,6 +156,8 @@ bool DocumentLifecycle::CanAdvanceTo(LifecycleState next_state) const {
return true;
if (next_state == kInPrePaint)
return true;
if (next_state == kInAccessibility)
return true;
break;
case kInPrePaint:
if (next_state == kPrePaintClean)
@ -157,6 +172,8 @@ bool DocumentLifecycle::CanAdvanceTo(LifecycleState next_state) const {
return true;
if (next_state == kInPrePaint)
return true;
if (next_state == kInAccessibility)
return true;
break;
case kInPaint:
if (next_state == kPaintClean)
@ -171,6 +188,8 @@ bool DocumentLifecycle::CanAdvanceTo(LifecycleState next_state) const {
return true;
if (next_state == kInPaint)
return true;
if (next_state == kInAccessibility)
return true;
break;
case kStopping:
return next_state == kStopped;
@ -190,8 +209,9 @@ bool DocumentLifecycle::CanRewindTo(LifecycleState next_state) const {
next_state == g_deprecated_transition_stack->To())
return true;
return state_ == kStyleClean || state_ == kAfterPerformLayout ||
state_ == kLayoutClean || state_ == kCompositingInputsClean ||
state_ == kPrePaintClean || state_ == kPaintClean;
state_ == kLayoutClean || state_ == kAccessibilityClean ||
state_ == kCompositingInputsClean || state_ == kPrePaintClean ||
state_ == kPaintClean;
}
#define DEBUG_STRING_CASE(StateName) \
@ -209,6 +229,8 @@ static WTF::String StateAsDebugString(
DEBUG_STRING_CASE(kInPerformLayout);
DEBUG_STRING_CASE(kAfterPerformLayout);
DEBUG_STRING_CASE(kLayoutClean);
DEBUG_STRING_CASE(kInAccessibility);
DEBUG_STRING_CASE(kAccessibilityClean);
DEBUG_STRING_CASE(kInCompositingInputsUpdate);
DEBUG_STRING_CASE(kCompositingInputsClean);
DEBUG_STRING_CASE(kInPrePaint);

@ -62,6 +62,11 @@ class CORE_EXPORT DocumentLifecycle {
kAfterPerformLayout,
kLayoutClean,
// In InAccessibility step, fire deferred accessibility events which
// require layout to be in a clean state.
kInAccessibility,
kAccessibilityClean,
kInCompositingInputsUpdate,
kCompositingInputsClean,

@ -2129,7 +2129,6 @@ const AtomicString& Element::computedRole() {
DocumentUpdateReason::kJavaScript);
}
AXContext ax_context(document, ui::kAXModeBasic);
ax_context.GetAXObjectCache().ProcessDeferredAccessibilityEvents(document);
return ax_context.GetAXObjectCache().ComputedRoleForNode(this);
}
@ -2143,7 +2142,6 @@ String Element::computedName() {
DocumentUpdateReason::kJavaScript);
}
AXContext ax_context(document, ui::kAXModeBasic);
ax_context.GetAXObjectCache().ProcessDeferredAccessibilityEvents(document);
return ax_context.GetAXObjectCache().ComputedNameForNode(this);
}

@ -2639,7 +2639,7 @@ void LocalFrame::DidResume() {
// TODO(yuzus): Figure out where these calls should really belong.
GetDocument()->DispatchHandleLoadStart();
GetDocument()->DispatchHandleLoadComplete();
GetDocument()->DispatchHandleLoadOrLayoutComplete();
}
void LocalFrame::MaybeLogAdClickNavigation() {

@ -1041,7 +1041,6 @@ void LocalFrameView::RunPostLifecycleSteps() {
base::AutoReset<bool> in_post_lifecycle_steps(&in_post_lifecycle_steps_,
true);
AllowThrottlingScope allow_throttling(*this);
RunAccessibilitySteps();
RunIntersectionObserverSteps();
if (mobile_friendliness_checker_)
mobile_friendliness_checker_->MaybeRecompute();
@ -2254,6 +2253,7 @@ bool LocalFrameView::UpdateLifecyclePhases(
// Only the following target states are supported.
DCHECK(target_state == DocumentLifecycle::kLayoutClean ||
target_state == DocumentLifecycle::kAccessibilityClean ||
target_state == DocumentLifecycle::kCompositingInputsClean ||
target_state == DocumentLifecycle::kPrePaintClean ||
target_state == DocumentLifecycle::kPaintClean);
@ -2434,6 +2434,13 @@ void LocalFrameView::UpdateLifecyclePhasesInternal(
DisallowLayoutInvalidationScope disallow_layout_invalidation(this);
#endif
DCHECK_GE(target_state, DocumentLifecycle::kAccessibilityClean);
run_more_lifecycle_phases = RunAccessibilityLifecyclePhase(target_state);
DCHECK(ShouldThrottleRendering() || !ExistingAXObjectCache() ||
Lifecycle().GetState() == DocumentLifecycle::kAccessibilityClean);
if (!run_more_lifecycle_phases)
return;
DEVTOOLS_TIMELINE_TRACE_EVENT_INSTANT_WITH_CATEGORIES(
TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "SetLayerTreeId",
inspector_set_layer_tree_id::Data, frame_.Get());
@ -2842,8 +2849,10 @@ void LocalFrameView::RunPaintLifecyclePhase(PaintBenchmarkMode benchmark_mode) {
GetPage()->Animator().ReportFrameAnimations(GetCompositorAnimationHost());
}
void LocalFrameView::RunAccessibilitySteps() {
TRACE_EVENT0("blink,benchmark", "LocalFrameView::RunAccessibilitySteps");
bool LocalFrameView::RunAccessibilityLifecyclePhase(
DocumentLifecycle::LifecycleState target_state) {
TRACE_EVENT0("blink,benchmark",
"LocalFrameView::RunAccessibilityLifecyclePhase");
SCOPED_UMA_AND_UKM_TIMER(EnsureUkmAggregator(),
LocalFrameUkmAggregator::kAccessibility);
@ -2854,10 +2863,14 @@ void LocalFrameView::RunAccessibilitySteps() {
ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) {
if (AXObjectCache* cache = frame_view.ExistingAXObjectCache()) {
frame_view.Lifecycle().AdvanceTo(DocumentLifecycle::kInAccessibility);
cache->ProcessDeferredAccessibilityEvents(
*frame_view.GetFrame().GetDocument());
frame_view.Lifecycle().AdvanceTo(DocumentLifecycle::kAccessibilityClean);
}
});
return target_state > DocumentLifecycle::kAccessibilityClean;
}
void LocalFrameView::EnqueueScrollAnchoringAdjustment(

@ -894,6 +894,8 @@ class CORE_EXPORT LocalFrameView final
// earlier if we don't need to run future lifecycle phases.
bool RunStyleAndLayoutLifecyclePhases(
DocumentLifecycle::LifecycleState target_state);
bool RunAccessibilityLifecyclePhase(
DocumentLifecycle::LifecycleState target_state);
bool RunCompositingInputsLifecyclePhase(
DocumentLifecycle::LifecycleState target_state);
bool RunPrePaintLifecyclePhase(
@ -914,7 +916,6 @@ class CORE_EXPORT LocalFrameView final
DocumentLifecycle& Lifecycle() const;
void RunAccessibilitySteps();
void RunIntersectionObserverSteps();
void RenderThrottlingStatusChanged();

@ -110,16 +110,19 @@
#include "ui/accessibility/mojom/ax_relative_bounds.mojom-blink.h"
// Prevent code that runs during the lifetime of the stack from altering the
// document lifecycle, for the main document, and the popup document if present.
// document lifecycle. Usually doc is the same as document_, but it can be
// different when it is a popup document. Because it's harmless to test both
// documents, even if they are the same, the scoped check is initialized for
// both documents.
// clang-format off
#if DCHECK_IS_ON()
#define SCOPED_DISALLOW_LIFECYCLE_TRANSITION() \
DocumentLifecycle::DisallowTransitionScope scoped(document_->Lifecycle()); \
DocumentLifecycle::DisallowTransitionScope scoped2( \
popup_document_ ? popup_document_->Lifecycle() \
: document_->Lifecycle());
#define SCOPED_DISALLOW_LIFECYCLE_TRANSITION(document) \
DocumentLifecycle::DisallowTransitionScope scoped1((document).Lifecycle()); \
DocumentLifecycle::DisallowTransitionScope scoped2(document_->Lifecycle())
#else
#define SCOPED_DISALLOW_LIFECYCLE_TRANSITION()
#define SCOPED_DISALLOW_LIFECYCLE_TRANSITION(document)
#endif // DCHECK_IS_ON()
// clang-format on
namespace blink {
@ -705,48 +708,54 @@ Node* AXObjectCacheImpl::FocusedElement() {
return focused_node;
}
void AXObjectCacheImpl::UpdateLayoutForAllDocuments() {
UpdateLifecycleIfNeeded(GetDocument());
if (Document* popup_document = GetPopupDocumentIfShowing())
UpdateLifecycleIfNeeded(*popup_document);
}
void AXObjectCacheImpl::UpdateLifecycleIfNeeded(Document& document) {
DCHECK(document.defaultView());
DCHECK(document.GetFrame());
DCHECK(document.View());
// TODO(accessibility) This temporarily requires kPrePaintClean as
// WebAXObject::UpdateLayout() did. Once a11y is moved to
// PostRunLifecycleTasks, restore to only require kLayoutClean.
// TODO(accessibility) Remove conditions and just update the lifecycle.
if (document.NeedsLayoutTreeUpdate() || document.View()->NeedsLayout() ||
document.Lifecycle().GetState() < DocumentLifecycle::kLayoutClean) {
document.Lifecycle().GetState() < DocumentLifecycle::kPrePaintClean) {
document.View()->UpdateAllLifecyclePhasesExceptPaint(
DocumentUpdateReason::kAccessibility);
}
}
void AXObjectCacheImpl::UpdateAXForAllDocuments() {
#if DCHECK_IS_ON()
DCHECK(!IsFrozen())
<< "Don't call UpdateAXForAllDocuments() here; layout and a11y are "
"already clean at the start of serialization.";
DCHECK(!updating_layout_and_ax_) << "Undesirable recursion.";
base::AutoReset<bool> updating(&updating_layout_and_ax_, true);
#endif
UpdateLayoutForAllDocuments();
// First update the layout for the main and popup document.
UpdateLifecycleIfNeeded(GetDocument());
if (Document* popup_document = GetPopupDocumentIfShowing())
UpdateLifecycleIfNeeded(*popup_document);
// Next flush all accessibility events and dirty objects, for both the main
// and popup document.
if (IsDirty())
if (IsMainDocumentDirty())
ProcessDeferredAccessibilityEvents(GetDocument());
if (IsPopupDocumentDirty())
ProcessDeferredAccessibilityEvents(*GetPopupDocumentIfShowing());
}
AXObject* AXObjectCacheImpl::GetOrCreateFocusedObjectFromNode(Node* node) {
#if DCHECK_IS_ON()
DCHECK(GetDocument().Lifecycle().GetState() >=
DocumentLifecycle::kAfterPerformLayout);
if (GetPopupDocumentIfShowing()) {
DCHECK(GetPopupDocumentIfShowing()->Lifecycle().GetState() >=
DocumentLifecycle::kAfterPerformLayout);
if (!node)
return nullptr;
// TODO(chrishtr): refactor to use UpdateLayoutForAllDocuments().
if (node->GetDocument() != GetDocument() &&
node->GetDocument().Lifecycle().GetState() <
DocumentLifecycle::kLayoutClean) {
// Node is in a different, unclean document. This can occur in an open
// popup. Ensure the popup document has a clean layout before trying to
// create an AXObject from a node in it.
if (node->GetDocument().View()) {
node->GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
DocumentUpdateReason::kAccessibility);
}
}
#endif
AXObject* obj = GetOrCreate(node);
if (!obj)
@ -1558,27 +1567,6 @@ AXObject* AXObjectCacheImpl::CreateAndInit(LayoutObject* layout_object,
return result;
}
if (!parent_if_known &&
(layout_object->IsText() || layout_object->IsPseudoElement() || !node)) {
// If the parent is not known, it means we are creating an AXObject at an
// arbitrary place in the tree. Ensure its parent has it as a
// child. an thus is connected to the root in both directions,
// and is not an orphan.
// This is accomplished by creating an AXObject for |layout_object|, by
// first asking the parent to create its children, and then returning the
// matching AXObject for |layout_object|.
// This prevents situations where we attempt to serialize a node
// and fail, because the parent does not reach it via its children.
// It is only known to be an issue with AXObjects backed by layout, where a
// change to layout has invalidated the inclusion of something in the tree.
// For now, do this only for text and pseudo content, as it is a smaller
// change, but consider doing it for more/all nodes in the future.
DCHECK(!use_axid)
<< "Cannot enforce an AXID when creating in the middle of the tree.";
parent->UpdateChildrenIfNecessary();
return Get(layout_object);
}
AXObject* new_obj = CreateFromRenderer(layout_object);
DCHECK(new_obj) << "Could not create AXObject for " << layout_object;
@ -1942,11 +1930,28 @@ void AXObjectCacheImpl::DeferTreeUpdateInternal(base::OnceClosure callback,
return;
}
#if DCHECK_IS_ON()
// TODO(accessibility) Restore this check. Currently must be removed because a
// loop in ProcessDeferredAccessibilityEvents() is allowed to queue deferred
// ChildrenChanged() events and process them.
// DCHECK(!tree_update_document->GetPage()->Animator().IsServicingAnimations()
// ||
// (tree_update_document->Lifecycle().GetState() <
// DocumentLifecycle::kInAccessibility ||
// tree_update_document->Lifecycle().StateAllowsDetach()))
// << "DeferTreeUpdateInternal should only be outside of the lifecycle or
// "
// "before the accessibility state:"
// << "\n* IsServicingAnimations: "
// << tree_update_document->GetPage()->Animator().IsServicingAnimations()
// << "\n* Lifecycle: " << tree_update_document->Lifecycle().ToString();
#endif
queue.push_back(MakeGarbageCollected<TreeUpdateParams>(
obj->GetNode(), obj->AXObjectID(), ComputeEventFrom(),
active_event_from_action_, ActiveEventIntents(), std::move(callback)));
// These events are fired during RunPostLifecycleTasks(),
// These events are fired during DocumentLifecycle::kInAccessibility,
// ensure there is a document lifecycle update scheduled.
ScheduleVisualUpdate(*tree_update_document);
}
@ -1977,11 +1982,30 @@ void AXObjectCacheImpl::DeferTreeUpdateInternal(base::OnceClosure callback,
return;
}
#if DCHECK_IS_ON()
// TODO(accessibility) Consider re-adding. However, it conflicts with some
// calls from HandleTextMarkerDataAdded(), which need to defer even when
// already in clean layout. Removing this is not dangerous -- it helped ensure
// that we weren't bothering to defer when layout is already clean. It's
// actually ok if that's wrong here or there.
// DCHECK(!tree_update_document.GetPage()->Animator().IsServicingAnimations()
// ||
// (tree_update_document.Lifecycle().GetState() <
// DocumentLifecycle::kInAccessibility ||
// tree_update_document.Lifecycle().StateAllowsDetach()))
// << "DeferTreeUpdateInternal should only be outside of the lifecycle or
// "
// "before the accessibility state:"
// << "\n* IsServicingAnimations: "
// << tree_update_document.GetPage()->Animator().IsServicingAnimations()
// << "\n* Lifecycle: " << tree_update_document.Lifecycle().ToString();
#endif
queue.push_back(MakeGarbageCollected<TreeUpdateParams>(
node, 0, ComputeEventFrom(), active_event_from_action_,
ActiveEventIntents(), std::move(callback)));
// These events are fired during RunPostLifecycleTasks(),
// These events are fired during DocumentLifecycle::kInAccessibility,
// ensure there is a document lifecycle update scheduled.
ScheduleVisualUpdate(tree_update_document);
}
@ -2061,7 +2085,7 @@ void AXObjectCacheImpl::UpdateReverseTextRelations(
void AXObjectCacheImpl::StyleChanged(const LayoutObject* layout_object) {
DCHECK(layout_object);
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(layout_object->GetDocument());
Node* node = GetClosestNodeForLayoutObject(layout_object);
if (node)
DeferTreeUpdate(&AXObjectCacheImpl::StyleChangedWithCleanLayout, node);
@ -2148,7 +2172,8 @@ void AXObjectCacheImpl::TextChangedWithCleanLayout(
if (obj) {
if (obj->RoleValue() == ax::mojom::blink::Role::kStaticText &&
obj->LastKnownIsIncludedInTreeValue()) {
if (InlineTextBoxAccessibilityEnabled()) {
Settings* settings = GetSettings();
if (settings && settings->GetInlineTextBoxAccessibilityEnabled()) {
// Update inline text box children.
ChildrenChangedWithCleanLayout(optional_node_for_relation_update, obj);
return;
@ -2202,19 +2227,13 @@ void AXObjectCacheImpl::DocumentTitleChanged() {
void AXObjectCacheImpl::UpdateCacheAfterNodeIsAttached(Node* node) {
DCHECK(node);
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(node->GetDocument());
Document* document = DynamicTo<Document>(node);
if (document) {
// A popup is being shown.
DCHECK(*document != GetDocument());
DCHECK(!popup_document_);
popup_document_ = document;
DCHECK(IsPopup(*document));
// Fire children changed on the focused element that owns this popup.
ChildrenChanged(GetDocument().FocusedElement());
return;
popup_document_ = document;
}
DeferTreeUpdate(
&AXObjectCacheImpl::UpdateCacheAfterNodeIsAttachedWithCleanLayout, node);
}
@ -2479,37 +2498,12 @@ void AXObjectCacheImpl::ChildrenChangedWithCleanLayout(Node* optional_node,
}
void AXObjectCacheImpl::ProcessDeferredAccessibilityEvents(Document& document) {
if (IsPopup(document)) {
// Only process popup document together with main document.
DCHECK_EQ(&document, GetPopupDocumentIfShowing());
// Since a change occurred in the popup, processing of both documents will
// be needed. A visual update on the main document will force this.
ScheduleVisualUpdate(GetDocument());
return;
}
ProcessDeferredAccessibilityEventsImpl(document);
DCHECK_EQ(document, GetDocument());
DCHECK(!processing_deferred_events_);
if (IsDirty()) {
if (GetPopupDocumentIfShowing()) {
UpdateLifecycleIfNeeded(*GetPopupDocumentIfShowing());
ProcessDeferredAccessibilityEventsImpl(*GetPopupDocumentIfShowing());
}
ProcessDeferredAccessibilityEventsImpl(document);
}
// Accessibility is now clean for both documents: AXObjects can be safely
// traversed and AXObject's properties can be safely fetched.
// TODO(accessibility) Now that both documents are always processed at the
// same time, consider modifying the InspectorAccessibilityAgent so that only
// the callback for the main document is needed.
for (auto agent : agents_) {
// Accessibility is now clean: AXObjects can be safely traversed and
// AXObject's properties can be safely fetched.
for (auto agent : agents_)
agent->AXReadyCallback(document);
if (GetPopupDocumentIfShowing())
agent->AXReadyCallback(*GetPopupDocumentIfShowing());
}
// TODO(chrishtr): Accessibility serializations should happen now, on the
// condition that enough time has passed since the last serialization.
@ -2519,6 +2513,16 @@ void AXObjectCacheImpl::ProcessDeferredAccessibilityEventsImpl(
Document& document) {
TRACE_EVENT0("accessibility", "ProcessDeferredAccessibilityEvents");
DCHECK(document.Lifecycle().GetState() >= DocumentLifecycle::kInAccessibility)
<< "Deferred events should only be processed during the "
"accessibility document lifecycle or later.";
// When tree updates are paused, IsDirty() will return false. In this
// situation we should not return early because we would never trigger the
// code that resumes the tree updates, inside ProcessCleanLayoutCallbacks.
if (!IsDirty() && !tree_updates_paused_)
return;
DCHECK(GetDocument().IsAccessibilityEnabled())
<< "ProcessDeferredAccessibilityEvents should not perform work when "
"accessibility is not enabled."
@ -2531,8 +2535,6 @@ void AXObjectCacheImpl::ProcessDeferredAccessibilityEventsImpl(
int loop_counter = 0;
#endif
base::AutoReset<bool> processing(&processing_deferred_events_, true);
do {
// Destroy and recreate any objects which are no longer valid, for example
// they used AXNodeObject and now must be an AXLayoutObject, or vice-versa.
@ -2581,6 +2583,8 @@ bool AXObjectCacheImpl::IsPopupDocumentDirty() const {
}
bool AXObjectCacheImpl::IsDirty() const {
if (tree_updates_paused_)
return false;
return IsMainDocumentDirty() || IsPopupDocumentDirty() ||
relation_cache_->IsDirty();
}
@ -2607,7 +2611,14 @@ bool AXObjectCacheImpl::IsPopup(Document& document) const {
<< "The popup document's owner should be in the main document.";
Page* main_page = GetDocument().GetPage();
DCHECK(main_page);
DCHECK_EQ(&document, popup_document_);
// TODO(accessibility) Verify that the main document's popup is |document|.
// PagePopupController* popup_controller =
// PagePopupController::From(*main_page);
// DCHECK(popup_controller);
// AXObject* popup_root_ax_object = popup_controller->RootAXObject();
// DCHECK(popup_root_ax_object);
// DCHECK_EQ(popup_root_ax_object->GetDocument(), &document)
// << "There can be only one active popup document.";
}
#endif
return is_popup;
@ -2678,8 +2689,7 @@ void AXObjectCacheImpl::ProcessInvalidatedObjects(Document& document) {
// TODO(accessibility) That may be the only example of this, in which case
// it could be handled in RoleChangedWithCleanLayout(), and the cached
// parent could be used.
AXObject* new_object = CreateAndInit(
node, AXObject::ComputeNonARIAParent(*this, node), retained_axid);
AXObject* new_object = CreateAndInit(node, nullptr, retained_axid);
if (new_object) {
// Any owned objects need to reset their parent_ to point to the
// new object.
@ -2758,7 +2768,7 @@ void AXObjectCacheImpl::ProcessInvalidatedObjects(Document& document) {
}
void AXObjectCacheImpl::ProcessCleanLayoutCallbacks(Document& document) {
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(document);
if (tree_updates_paused_) {
ChildrenChangedWithCleanLayout(nullptr, GetOrCreate(&document));
@ -2875,7 +2885,7 @@ void AXObjectCacheImpl::PostNotification(AXObject* object,
// It's possible for FireAXEventImmediately to post another notification.
// If we're still in the accessibility document lifecycle, fire these events
// immediately rather than deferring them.
if (processing_deferred_events_) {
if (document.Lifecycle().GetState() == DocumentLifecycle::kInAccessibility) {
FireAXEventImmediately(object, event_type, ComputeEventFrom(),
active_event_from_action_, ActiveEventIntents());
return;
@ -2886,7 +2896,7 @@ void AXObjectCacheImpl::PostNotification(AXObject* object,
object, event_type, ComputeEventFrom(), active_event_from_action_,
ActiveEventIntents()));
// These events are fired during RunPostLifecycleTasks(),
// These events are fired during DocumentLifecycle::kInAccessibility,
// ensure there is a visual update scheduled.
ScheduleVisualUpdate(document);
}
@ -2907,7 +2917,9 @@ void AXObjectCacheImpl::ScheduleVisualUpdate(Document& document) {
return;
if (!frame_view->CanThrottleRendering() &&
!document.GetPage()->Animator().IsServicingAnimations()) {
(!document.GetPage()->Animator().IsServicingAnimations() ||
document.Lifecycle().GetState() >=
DocumentLifecycle::kInAccessibility)) {
page->Animator().ScheduleVisualUpdate(document.GetFrame());
}
}
@ -2918,7 +2930,8 @@ void AXObjectCacheImpl::FireTreeUpdatedEventImmediately(
ax::mojom::blink::Action event_from_action,
const BlinkAXEventIntentsSet& event_intents,
base::OnceClosure callback) {
DCHECK(processing_deferred_events_);
DCHECK_GE(document.Lifecycle().GetState(),
DocumentLifecycle::kInAccessibility);
base::AutoReset<ax::mojom::blink::EventFrom> event_from_resetter(
&active_event_from_, event_from);
@ -2935,6 +2948,9 @@ void AXObjectCacheImpl::FireAXEventImmediately(
ax::mojom::blink::EventFrom event_from,
ax::mojom::blink::Action event_from_action,
const BlinkAXEventIntentsSet& event_intents) {
DCHECK_GE(obj->GetDocument()->Lifecycle().GetState(),
DocumentLifecycle::kInAccessibility);
#if DCHECK_IS_ON()
// Make sure none of the layout views are in the process of being laid out.
// Notifications should only be sent after the layoutObject has finished
@ -2945,7 +2961,7 @@ void AXObjectCacheImpl::FireAXEventImmediately(
DCHECK(!layout_object->View()->GetLayoutState());
}
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(*obj->GetDocument());
#endif // DCHECK_IS_ON()
if (event_type == ax::mojom::blink::Event::kChildrenChanged &&
@ -3008,7 +3024,7 @@ void AXObjectCacheImpl::ListboxSelectedChildrenChanged(
}
void AXObjectCacheImpl::ListboxActiveIndexChanged(HTMLSelectElement* select) {
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(select->GetDocument());
auto* ax_object = DynamicTo<AXListBox>(Get(select));
if (!ax_object)
@ -3057,7 +3073,7 @@ void AXObjectCacheImpl::HandleAriaExpandedChangeWithCleanLayout(Node* node) {
if (!node)
return;
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(node->GetDocument());
DCHECK(!node->GetDocument().NeedsLayoutTreeUpdateForNode(*node));
if (AXObject* obj = GetOrCreate(node))
@ -3081,31 +3097,15 @@ void AXObjectCacheImpl::HandleAriaPressedChangedWithCleanLayout(
PostNotification(element, ax::mojom::blink::Event::kCheckedStateChanged);
}
// In single selection containers, selection follows focus, so a selection
// changed event must be fired. This ensures the AT is notified that the
// selected state has changed, so that it does not read "unselected" as
// the user navigates through the items. The event generator will handle
// the correct events as long as the old and newly selected objects are marked
// dirty.
void AXObjectCacheImpl::HandleAriaSelectedChangedWithCleanLayout(Node* node) {
DCHECK(node);
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(node->GetDocument());
DCHECK(!node->GetDocument().NeedsLayoutTreeUpdateForNode(*node));
AXObject* obj = Get(node);
if (!obj)
return;
// Mark the previous selected item dirty if it was selected viaa "selection
// follows focus".
if (last_selected_from_active_descendant_)
MarkElementDirtyWithCleanLayout(last_selected_from_active_descendant_);
// Mark the newly selected item dirty, and track it for use in the future.
MarkAXObjectDirtyWithCleanLayout(obj);
if (obj->IsSelectedFromFocus())
last_selected_from_active_descendant_ = node;
PostNotification(obj, ax::mojom::Event::kCheckedStateChanged);
AXObject* listbox = obj->ParentObjectUnignored();
@ -3139,6 +3139,15 @@ void AXObjectCacheImpl::HandleNodeGainedFocusWithCleanLayout(Node* node) {
if (!node || !node->GetDocument().View())
return;
// TODO(chrishtr): refactor to use UpdateLifecycleIfNeeded.
if (node->GetDocument().NeedsLayoutTreeUpdateForNode(*node)) {
// This should only occur when focus goes into a popup document. The main
// document has an updated layout, but the popup does not.
DCHECK_NE(document_, node->GetDocument());
node->GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
DocumentUpdateReason::kAccessibility);
}
AXObject* obj = GetOrCreateFocusedObjectFromNode(node);
if (!obj)
return;
@ -3252,7 +3261,7 @@ void AXObjectCacheImpl::HandleAriaHiddenChangedWithCleanLayout(Node* node) {
if (!node)
return;
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(node->GetDocument());
DCHECK(!node->GetDocument().NeedsLayoutTreeUpdateForNode(*node));
AXObject* obj = GetOrCreate(node);
@ -3516,7 +3525,7 @@ void AXObjectCacheImpl::RemoveValidationMessageObjectWithCleanLayout(
void AXObjectCacheImpl::HandleValidationMessageVisibilityChanged(
const Node* form_control) {
DCHECK(form_control);
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(form_control->GetDocument());
DeferTreeUpdate(&AXObjectCacheImpl::
HandleValidationMessageVisibilityChangedWithCleanLayout,
@ -3697,13 +3706,6 @@ void AXObjectCacheImpl::MarkAXObjectDirtyWithCleanLayoutHelper(
if (!obj)
return;
// If the content is inside the popup, mark the owning element dirty.
// TODO(aleventhal): not sure why this works, but now that we run a11y in
// PostRunLifecycleTasks(), we need this, otherwise the pending updates in
// the popup aren't processed.
if (IsPopup(*obj->GetDocument()))
MarkElementDirty(GetDocument().FocusedElement());
WebLocalFrameImpl* webframe = WebLocalFrameImpl::FromFrame(
obj->GetDocument()->AXObjectCacheOwner().GetFrame());
if (webframe && webframe->Client()) {
@ -3739,7 +3741,6 @@ void AXObjectCacheImpl::MarkAXSubtreeDirtyWithCleanLayout(AXObject* obj) {
void AXObjectCacheImpl::MarkAXObjectDirty(AXObject* obj) {
if (!obj)
return;
base::OnceClosure callback =
WTF::BindOnce(&AXObjectCacheImpl::MarkAXObjectDirtyWithCleanLayout,
WrapWeakPersistent(this), WrapWeakPersistent(obj));
@ -3821,7 +3822,7 @@ void AXObjectCacheImpl::HandleFocusedUIElementChanged(
#if DCHECK_IS_ON()
// The focus can be in a different document when a popup is open.
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(focused_doc);
#endif // DCHECK_IS_ON()
if (focused_doc.GetPage() && focused_doc.GetPage()->InsidePortal())
@ -4019,10 +4020,7 @@ void AXObjectCacheImpl::SerializeDirtyObjectsAndEvents(
size_t num_remaining_objects_to_serialize =
dirty_objects_.size() + kMaxExtraDirtyObjectsToSerialize;
DCHECK_GE(GetDocument().Lifecycle().GetState(),
DocumentLifecycle::kLayoutClean);
DCHECK(!popup_document_ || popup_document_->Lifecycle().GetState() >=
DocumentLifecycle::kLayoutClean);
UpdateLayoutForAllDocuments();
while (!dirty_objects_.empty() && --num_remaining_objects_to_serialize > 0) {
AXDirtyObject* current_dirty_object = std::move(dirty_objects_.front());
@ -4052,7 +4050,7 @@ void AXObjectCacheImpl::SerializeDirtyObjectsAndEvents(
// ends up skipping it. That's probably a Blink bug if that happens, but
// still we need to make sure we don't keep trying the same object over
// again.
if (already_serialized_ids.Contains(obj->AXObjectID()))
if (!already_serialized_ids.insert(obj->AXObjectID()).is_new_entry)
continue; // No insertion, was already present.
ui::AXTreeUpdate update;
@ -4071,20 +4069,11 @@ void AXObjectCacheImpl::SerializeDirtyObjectsAndEvents(
}
DCHECK_GT(update.nodes.size(), 0U);
for (auto& node : update.nodes) {
DCHECK(node.id);
already_serialized_ids.insert(node.id);
}
DCHECK(already_serialized_ids.Contains(obj->AXObjectID()))
<< "Did not serialize original node, so it was probably not included "
"in its parent's children, and should never have been created in "
"the first place: "
<< obj->ToString(true)
<< "\nParent: " << obj->ParentObjectIncludedInTree()->ToString(true)
<< "\nIndex in parent: " << obj->IndexInParent();
updates.push_back(update);
}
@ -4156,7 +4145,7 @@ void AXObjectCacheImpl::HandleEditableTextContentChanged(Node* node) {
if (!node)
return;
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(node->GetDocument());
DeferTreeUpdate(
&AXObjectCacheImpl::HandleEditableTextContentChangedWithCleanLayout,
@ -4272,7 +4261,7 @@ void AXObjectCacheImpl::HandleUpdateActiveMenuOptionWithCleanLayout(
}
void AXObjectCacheImpl::DidShowMenuListPopup(LayoutObject* menu_list) {
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(menu_list->GetDocument());
DCHECK(menu_list->GetNode());
DeferTreeUpdate(&AXObjectCacheImpl::DidShowMenuListPopupWithCleanLayout,
@ -4291,7 +4280,7 @@ void AXObjectCacheImpl::DidShowMenuListPopupWithCleanLayout(Node* menu_list) {
}
void AXObjectCacheImpl::DidHideMenuListPopup(LayoutObject* menu_list) {
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(menu_list->GetDocument());
DCHECK(menu_list->GetNode());
DeferTreeUpdate(&AXObjectCacheImpl::DidHideMenuListPopupWithCleanLayout,
@ -4310,22 +4299,18 @@ void AXObjectCacheImpl::DidHideMenuListPopupWithCleanLayout(Node* menu_list) {
}
void AXObjectCacheImpl::HandleLoadStart(Document* document) {
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
if (!IsPopup(*document)) {
DeferTreeUpdate(&AXObjectCacheImpl::EnsurePostNotification, document,
ax::mojom::blink::Event::kLoadStart);
}
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(*document);
MarkAXObjectDirty(Get(document));
DeferTreeUpdate(&AXObjectCacheImpl::EnsurePostNotification, document,
ax::mojom::blink::Event::kLoadStart);
}
void AXObjectCacheImpl::HandleLoadComplete(Document* document) {
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(*document);
// Popups do not need to fire a load complete message.
if (!IsPopup(*document)) {
AddPermissionStatusListener();
DeferTreeUpdate(&AXObjectCacheImpl::HandleLoadCompleteWithCleanLayout,
document);
}
AddPermissionStatusListener();
DeferTreeUpdate(&AXObjectCacheImpl::HandleLoadCompleteWithCleanLayout,
document);
}
void AXObjectCacheImpl::HandleLoadCompleteWithCleanLayout(Node* document_node) {
@ -4344,14 +4329,7 @@ void AXObjectCacheImpl::HandleLoadCompleteWithCleanLayout(Node* document_node) {
}
void AXObjectCacheImpl::HandleLayoutComplete(Document* document) {
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
DCHECK(document);
// Do not fire kLayoutComplete for popup document.
if (document == GetPopupDocumentIfShowing())
return;
// TODO(accessibility) What is the purpose of firing kLayoutComplete?
// Do we even need this?
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(*document);
if (document->Lifecycle().GetState() >=
DocumentLifecycle::kAfterPerformLayout) {
PostNotification(GetOrCreate(document),
@ -4366,7 +4344,7 @@ void AXObjectCacheImpl::HandleScrolledToAnchor(const Node* anchor_node) {
if (!anchor_node)
return;
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(anchor_node->GetDocument());
AXObject* obj = GetOrCreate(anchor_node->GetLayoutObject());
if (!obj)
@ -4399,7 +4377,7 @@ void AXObjectCacheImpl::SerializerClearedNode(AXID id) {
void AXObjectCacheImpl::HandleScrollPositionChanged(
LocalFrameView* frame_view) {
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(*frame_view->GetFrame().GetDocument());
InvalidateBoundingBoxForFixedOrStickyPosition();
MarkElementDirty(document_);
@ -4409,7 +4387,7 @@ void AXObjectCacheImpl::HandleScrollPositionChanged(
void AXObjectCacheImpl::HandleScrollPositionChanged(
LayoutObject* layout_object) {
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(layout_object->GetDocument());
InvalidateBoundingBoxForFixedOrStickyPosition();
Node* node = GetClosestNodeForLayoutObject(layout_object);
if (node) {
@ -4420,7 +4398,7 @@ void AXObjectCacheImpl::HandleScrollPositionChanged(
}
const AtomicString& AXObjectCacheImpl::ComputedRoleForNode(Node* node) {
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(node->GetDocument());
AXObject* obj = GetOrCreate(node);
if (!obj)
@ -4429,7 +4407,7 @@ const AtomicString& AXObjectCacheImpl::ComputedRoleForNode(Node* node) {
}
String AXObjectCacheImpl::ComputedNameForNode(Node* node) {
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(node->GetDocument());
AXObject* obj = GetOrCreate(node);
if (!obj)
return "";
@ -4455,7 +4433,7 @@ void AXObjectCacheImpl::OnTouchAccessibilityHover(const gfx::Point& location) {
void AXObjectCacheImpl::SetCanvasObjectBounds(HTMLCanvasElement* canvas,
Element* element,
const LayoutRect& rect) {
SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
SCOPED_DISALLOW_LIFECYCLE_TRANSITION(element->GetDocument());
AXObject* obj = GetOrCreate(element);
if (!obj)
@ -4533,7 +4511,6 @@ void AXObjectCacheImpl::Trace(Visitor* visitor) const {
visitor->Trace(agents_);
visitor->Trace(document_);
visitor->Trace(popup_document_);
visitor->Trace(last_selected_from_active_descendant_);
visitor->Trace(accessible_node_mapping_);
visitor->Trace(layout_object_mapping_);
visitor->Trace(node_object_mapping_);

@ -558,6 +558,7 @@ class MODULES_EXPORT AXObjectCacheImpl
mojo::Remote<mojom::blink::RenderAccessibilityHost>&
GetOrCreateRemoteRenderAccessibilityHost();
void ProcessDeferredAccessibilityEventsImpl(Document&);
void UpdateLayoutForAllDocuments();
void UpdateLifecycleIfNeeded(Document& document);
bool IsMainDocumentDirty() const;
@ -663,17 +664,8 @@ class MODULES_EXPORT AXObjectCacheImpl
// AriaModalPrunesAXTree setting enabled, such as Mac.
WeakMember<AXObject> active_aria_modal_dialog_;
// If non-null, this is the node that the current aria-activedescendant caused
// to have the selected state.
WeakMember<Node> last_selected_from_active_descendant_;
std::unique_ptr<AXRelationCache> relation_cache_;
bool processing_deferred_events_ = false;
#if DCHECK_IS_ON()
bool updating_layout_and_ax_ = false;
#endif
// Verified when finalizing.
bool has_been_disposed_ = false;

@ -113,6 +113,7 @@ TEST_F(AccessibilityTest, PauseUpdatesAfterMaxNumberQueued) {
ax_object_cache->DeferTreeUpdate(
&AXObjectCacheImpl::ChildrenChangedWithCleanLayout, ax_obj);
}
document.Lifecycle().AdvanceTo(DocumentLifecycle::kInAccessibility);
ax_object_cache->ProcessCleanLayoutCallbacks(document);
ASSERT_EQ(0u, MockAXObject::num_children_changed_calls_);

@ -25,7 +25,8 @@ void AccessibilityTest::SetUp() {
AXObjectCacheImpl& AccessibilityTest::GetAXObjectCache() const {
DCHECK(GetDocument().View());
GetDocument().View()->UpdateAllLifecyclePhasesForTest();
GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
DocumentUpdateReason::kAccessibility);
auto* ax_object_cache =
To<AXObjectCacheImpl>(GetDocument().ExistingAXObjectCache());
DCHECK(ax_object_cache);

@ -1317,6 +1317,7 @@ WebAXObject WebAXObject::FromWebDocument(const WebDocument& web_document) {
const Document* document = web_document.ConstUnwrap<Document>();
auto* cache = To<AXObjectCacheImpl>(document->ExistingAXObjectCache());
DCHECK(cache);
cache->UpdateAXForAllDocuments();
return WebAXObject(cache->GetOrCreate(document));
}

@ -139,8 +139,8 @@ async_test((t) => {
]);
// SelectedTextChanged at 10.
expectedSelectedTextChangedIntents.push([
'AXEventIntent(delete,deleteContentBackward,none,none)',
'AXEventIntent(setSelection,none,character,forward)',
'AXEventIntent(delete,deleteContentBackward,none,none)',
]);
eventSender.keyDown('Backspace', []);
}, 'Ensures that moving the cursor in a contentEditable sends a selected text change notification, and typing in a contentEditable sends both a value changed and selected text changed notification.');