Commit a13d2391 authored by Jacobo Aragunde Pérez's avatar Jacobo Aragunde Pérez Committed by Commit Bot

Postpone ATK window activated events until AT-SPI is ready.

AT-SPI sets up a number of ATK event listeners. If Chromium emits
events too early, they won't be detected by AT-SPI.

The initialization of AT-SPI listeners happens at
spi_atk_register_event_listeners in the at-spi2-atk library. It also
sets up a key event listener, which is registered by Chromium at
AtkUtilAuraLinuxAddKeyEventListener. We use the registration of the
key event listener as an indication of AT-SPI being ready, due to our
internal knowledge of the internals of the AT-SPI/ATK bridge.

If there are no key event listeners, we consider AT-SPI is not ready,
so window activated events are queued. As soon as a key event
listener is registered, events will be run and never queued again.
A window deactivated event would cancel the queued activated event.

Bug: 1062755
Change-Id: I2d1a1516bed2b8437df664d69399b72af02eb4a8
AX-Relnotes: Linux: send Chromium's first window activation event.
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2312818Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Commit-Queue: Jacobo Aragunde Pérez <jaragunde@igalia.com>
Cr-Commit-Position: refs/heads/master@{#800502}
parent a083c6b7
......@@ -53,11 +53,32 @@ static KeySnoopFuncMap& GetActiveKeySnoopFunctions() {
return *active_key_snoop_functions;
}
using AXPlatformNodeSet = std::unordered_set<ui::AXPlatformNodeAuraLinux*>;
static AXPlatformNodeSet& GetNodesWithPostponedEvents() {
static base::NoDestructor<AXPlatformNodeSet> nodes_with_postponed_events_list;
return *nodes_with_postponed_events_list;
}
static void RunPostponedEvents() {
for (ui::AXPlatformNodeAuraLinux* entry : GetNodesWithPostponedEvents()) {
entry->RunPostponedEvents();
}
GetNodesWithPostponedEvents().clear();
}
static guint AtkUtilAuraLinuxAddKeyEventListener(
AtkKeySnoopFunc key_snoop_function,
gpointer data) {
static guint current_key_event_listener_id = 0;
// AtkUtilAuraLinuxAddKeyEventListener is called by
// spi_atk_register_event_listeners in the at-spi2-atk library after it has
// initialized all its other listeners. Our internal knowledge of the
// internals of the AT-SPI/ATK bridge allows us to use this call as an
// indication of AT-SPI being ready, so we can finally run any events that had
// been waiting.
ui::AtkUtilAuraLinux::GetInstance()->SetAtSpiReady(true);
current_key_event_listener_id++;
GetActiveKeySnoopFunctions()[current_key_event_listener_id] =
std::make_pair(key_snoop_function, data);
......@@ -147,4 +168,22 @@ DiscardAtkKeyEvent AtkUtilAuraLinux::HandleAtkKeyEvent(
return discard ? DiscardAtkKeyEvent::Discard : DiscardAtkKeyEvent::Retain;
}
bool AtkUtilAuraLinux::IsAtSpiReady() {
return at_spi_ready_;
}
void AtkUtilAuraLinux::SetAtSpiReady(bool ready) {
at_spi_ready_ = ready;
if (ready)
RunPostponedEvents();
}
void AtkUtilAuraLinux::PostponeEventsFor(AXPlatformNodeAuraLinux* node) {
GetNodesWithPostponedEvents().insert(node);
}
void AtkUtilAuraLinux::CancelPostponedEventsFor(AXPlatformNodeAuraLinux* node) {
GetNodesWithPostponedEvents().erase(node);
}
} // namespace ui
......@@ -10,6 +10,7 @@
#include "base/macros.h"
#include "base/memory/singleton.h"
#include "ui/accessibility/ax_export.h"
#include "ui/accessibility/platform/ax_platform_node_auralinux.h"
namespace ui {
......@@ -42,6 +43,15 @@ class AX_EXPORT AtkUtilAuraLinux {
void InitializeAsync();
void InitializeForTesting();
bool IsAtSpiReady();
void SetAtSpiReady(bool ready);
// Nodes with postponed events will get the function RunPostponedEvents()
// called as soon as AT-SPI is detected to be ready
void PostponeEventsFor(AXPlatformNodeAuraLinux* node);
void CancelPostponedEventsFor(AXPlatformNodeAuraLinux* node);
static DiscardAtkKeyEvent HandleAtkKeyEvent(AtkKeyEventStruct* key_event);
private:
......@@ -51,6 +61,8 @@ class AX_EXPORT AtkUtilAuraLinux {
void PlatformInitializeAsync();
bool at_spi_ready_ = false;
DISALLOW_COPY_AND_ASSIGN(AtkUtilAuraLinux);
};
......
......@@ -95,4 +95,19 @@ TEST_F(AtkUtilAuraLinuxTest, KeySnooping) {
EXPECT_EQ(keyval_seen, 0);
}
TEST_F(AtkUtilAuraLinuxTest, AtSpiReady) {
AtkUtilAuraLinux* atk_util = AtkUtilAuraLinux::GetInstance();
EXPECT_FALSE(atk_util->IsAtSpiReady());
// In a normal browser execution, when a key event listener is added it means
// the AT-SPI bridge has done it as part of its initialization, so it is set
// as enabled.
AtkKeySnoopFunc key_snoop_func =
reinterpret_cast<AtkKeySnoopFunc>(+[](AtkKeyEventStruct* key_event) {});
atk_add_key_event_listener(key_snoop_func, nullptr);
EXPECT_TRUE(atk_util->IsAtSpiReady());
}
} // namespace ui
......@@ -3276,6 +3276,9 @@ AXPlatformNodeAuraLinux::~AXPlatformNodeAuraLinux() {
DestroyAtkObjects();
if (window_activate_event_postponed_)
AtkUtilAuraLinux::GetInstance()->CancelPostponedEventsFor(this);
SetWeakGPtrToAtkObject(&document_parent_, nullptr);
}
......@@ -3992,6 +3995,13 @@ void AXPlatformNodeAuraLinux::OnAlertShown() {
ATK_STATE_SHOWING, TRUE);
}
void AXPlatformNodeAuraLinux::RunPostponedEvents() {
if (window_activate_event_postponed_) {
OnWindowActivated();
window_activate_event_postponed_ = false;
}
}
void AXPlatformNodeAuraLinux::NotifyAccessibilityEvent(
ax::mojom::Event event_type) {
if (!GetOrCreateAtkObject())
......@@ -4043,10 +4053,20 @@ void AXPlatformNodeAuraLinux::NotifyAccessibilityEvent(
OnInvalidStatusChanged();
break;
case ax::mojom::Event::kWindowActivated:
OnWindowActivated();
if (AtkUtilAuraLinux::GetInstance()->IsAtSpiReady()) {
OnWindowActivated();
} else {
AtkUtilAuraLinux::GetInstance()->PostponeEventsFor(this);
window_activate_event_postponed_ = true;
}
break;
case ax::mojom::Event::kWindowDeactivated:
OnWindowDeactivated();
if (AtkUtilAuraLinux::GetInstance()->IsAtSpiReady()) {
OnWindowDeactivated();
} else {
AtkUtilAuraLinux::GetInstance()->CancelPostponedEventsFor(this);
window_activate_event_postponed_ = false;
}
break;
case ax::mojom::Event::kWindowVisibilityChanged:
OnWindowVisibilityChanged();
......
......@@ -225,6 +225,7 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
void OnWindowVisibilityChanged();
void OnScrolledToAnchor();
void OnAlertShown();
void RunPostponedEvents();
void ResendFocusSignalsForCurrentlyFocusedNode();
bool SupportsSelectionWithAtkSelection();
......@@ -426,6 +427,8 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
// The default ATK text attributes for this node.
TextAttributeList default_text_attributes_;
bool window_activate_event_postponed_ = false;
DISALLOW_COPY_AND_ASSIGN(AXPlatformNodeAuraLinux);
};
......
......@@ -8,6 +8,7 @@
#include <vector>
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/platform/atk_util_auralinux.h"
#include "ui/accessibility/platform/ax_platform_node_auralinux.h"
#include "ui/accessibility/platform/ax_platform_node_unittest.h"
#include "ui/accessibility/platform/test_ax_node_wrapper.h"
......@@ -1620,6 +1621,10 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkWindowActive) {
}),
&saw_active_focus_state_change);
// ATK window activated event will be held until AT-SPI bridge is ready. We
// work that around by faking its state.
ui::AtkUtilAuraLinux::GetInstance()->SetAtSpiReady(true);
{
ActivationTester tester(root_atk_object);
EXPECT_FALSE(tester.IsActivatedInStateSet());
......@@ -1646,6 +1651,70 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkWindowActive) {
g_object_unref(root_atk_object);
}
TEST_F(AXPlatformNodeAuraLinuxTest, TestPostponedAtkWindowActive) {
AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kWindow;
Init(root);
AtkObject* root_atk_object(GetRootAtkObject());
EXPECT_TRUE(ATK_IS_OBJECT(root_atk_object));
g_object_ref(root_atk_object);
EXPECT_TRUE(ATK_IS_WINDOW(root_atk_object));
AtkUtilAuraLinux* atk_util = ui::AtkUtilAuraLinux::GetInstance();
{
ActivationTester tester(root_atk_object);
EXPECT_FALSE(tester.IsActivatedInStateSet());
static_cast<AXPlatformNodeAuraLinux*>(GetRootPlatformNode())
->NotifyAccessibilityEvent(ax::mojom::Event::kWindowActivated);
// ATK window activated event will be held until AT-SPI bridge is ready.
EXPECT_FALSE(tester.saw_activate_);
EXPECT_FALSE(tester.saw_deactivate_);
// We force the AT-SPI ready flag to flush any held events.
atk_util->SetAtSpiReady(true);
EXPECT_TRUE(tester.saw_activate_);
EXPECT_FALSE(tester.saw_deactivate_);
EXPECT_TRUE(tester.IsActivatedInStateSet());
}
{
ActivationTester tester(root_atk_object);
static_cast<AXPlatformNodeAuraLinux*>(GetRootPlatformNode())
->NotifyAccessibilityEvent(ax::mojom::Event::kWindowDeactivated);
EXPECT_FALSE(tester.saw_activate_);
EXPECT_TRUE(tester.saw_deactivate_);
EXPECT_FALSE(tester.IsActivatedInStateSet());
}
{
atk_util->SetAtSpiReady(false);
ActivationTester tester(root_atk_object);
static_cast<AXPlatformNodeAuraLinux*>(GetRootPlatformNode())
->NotifyAccessibilityEvent(ax::mojom::Event::kWindowActivated);
// Window deactivated will cancel the previously held activated event.
static_cast<AXPlatformNodeAuraLinux*>(GetRootPlatformNode())
->NotifyAccessibilityEvent(ax::mojom::Event::kWindowDeactivated);
// We force the AT-SPI ready flag to flush any held events.
atk_util->SetAtSpiReady(true);
// No events seen because they cancelled each other.
EXPECT_FALSE(tester.saw_activate_);
EXPECT_FALSE(tester.saw_deactivate_);
}
g_object_unref(root_atk_object);
}
//
// AtkWindow interface and iconified state
//
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment