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() { ...@@ -53,11 +53,32 @@ static KeySnoopFuncMap& GetActiveKeySnoopFunctions() {
return *active_key_snoop_functions; 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( static guint AtkUtilAuraLinuxAddKeyEventListener(
AtkKeySnoopFunc key_snoop_function, AtkKeySnoopFunc key_snoop_function,
gpointer data) { gpointer data) {
static guint current_key_event_listener_id = 0; 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++; current_key_event_listener_id++;
GetActiveKeySnoopFunctions()[current_key_event_listener_id] = GetActiveKeySnoopFunctions()[current_key_event_listener_id] =
std::make_pair(key_snoop_function, data); std::make_pair(key_snoop_function, data);
...@@ -147,4 +168,22 @@ DiscardAtkKeyEvent AtkUtilAuraLinux::HandleAtkKeyEvent( ...@@ -147,4 +168,22 @@ DiscardAtkKeyEvent AtkUtilAuraLinux::HandleAtkKeyEvent(
return discard ? DiscardAtkKeyEvent::Discard : DiscardAtkKeyEvent::Retain; 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 } // namespace ui
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/singleton.h" #include "base/memory/singleton.h"
#include "ui/accessibility/ax_export.h" #include "ui/accessibility/ax_export.h"
#include "ui/accessibility/platform/ax_platform_node_auralinux.h"
namespace ui { namespace ui {
...@@ -42,6 +43,15 @@ class AX_EXPORT AtkUtilAuraLinux { ...@@ -42,6 +43,15 @@ class AX_EXPORT AtkUtilAuraLinux {
void InitializeAsync(); void InitializeAsync();
void InitializeForTesting(); 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); static DiscardAtkKeyEvent HandleAtkKeyEvent(AtkKeyEventStruct* key_event);
private: private:
...@@ -51,6 +61,8 @@ class AX_EXPORT AtkUtilAuraLinux { ...@@ -51,6 +61,8 @@ class AX_EXPORT AtkUtilAuraLinux {
void PlatformInitializeAsync(); void PlatformInitializeAsync();
bool at_spi_ready_ = false;
DISALLOW_COPY_AND_ASSIGN(AtkUtilAuraLinux); DISALLOW_COPY_AND_ASSIGN(AtkUtilAuraLinux);
}; };
......
...@@ -95,4 +95,19 @@ TEST_F(AtkUtilAuraLinuxTest, KeySnooping) { ...@@ -95,4 +95,19 @@ TEST_F(AtkUtilAuraLinuxTest, KeySnooping) {
EXPECT_EQ(keyval_seen, 0); 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 } // namespace ui
...@@ -3276,6 +3276,9 @@ AXPlatformNodeAuraLinux::~AXPlatformNodeAuraLinux() { ...@@ -3276,6 +3276,9 @@ AXPlatformNodeAuraLinux::~AXPlatformNodeAuraLinux() {
DestroyAtkObjects(); DestroyAtkObjects();
if (window_activate_event_postponed_)
AtkUtilAuraLinux::GetInstance()->CancelPostponedEventsFor(this);
SetWeakGPtrToAtkObject(&document_parent_, nullptr); SetWeakGPtrToAtkObject(&document_parent_, nullptr);
} }
...@@ -3992,6 +3995,13 @@ void AXPlatformNodeAuraLinux::OnAlertShown() { ...@@ -3992,6 +3995,13 @@ void AXPlatformNodeAuraLinux::OnAlertShown() {
ATK_STATE_SHOWING, TRUE); ATK_STATE_SHOWING, TRUE);
} }
void AXPlatformNodeAuraLinux::RunPostponedEvents() {
if (window_activate_event_postponed_) {
OnWindowActivated();
window_activate_event_postponed_ = false;
}
}
void AXPlatformNodeAuraLinux::NotifyAccessibilityEvent( void AXPlatformNodeAuraLinux::NotifyAccessibilityEvent(
ax::mojom::Event event_type) { ax::mojom::Event event_type) {
if (!GetOrCreateAtkObject()) if (!GetOrCreateAtkObject())
...@@ -4043,10 +4053,20 @@ void AXPlatformNodeAuraLinux::NotifyAccessibilityEvent( ...@@ -4043,10 +4053,20 @@ void AXPlatformNodeAuraLinux::NotifyAccessibilityEvent(
OnInvalidStatusChanged(); OnInvalidStatusChanged();
break; break;
case ax::mojom::Event::kWindowActivated: case ax::mojom::Event::kWindowActivated:
OnWindowActivated(); if (AtkUtilAuraLinux::GetInstance()->IsAtSpiReady()) {
OnWindowActivated();
} else {
AtkUtilAuraLinux::GetInstance()->PostponeEventsFor(this);
window_activate_event_postponed_ = true;
}
break; break;
case ax::mojom::Event::kWindowDeactivated: case ax::mojom::Event::kWindowDeactivated:
OnWindowDeactivated(); if (AtkUtilAuraLinux::GetInstance()->IsAtSpiReady()) {
OnWindowDeactivated();
} else {
AtkUtilAuraLinux::GetInstance()->CancelPostponedEventsFor(this);
window_activate_event_postponed_ = false;
}
break; break;
case ax::mojom::Event::kWindowVisibilityChanged: case ax::mojom::Event::kWindowVisibilityChanged:
OnWindowVisibilityChanged(); OnWindowVisibilityChanged();
......
...@@ -225,6 +225,7 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase { ...@@ -225,6 +225,7 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
void OnWindowVisibilityChanged(); void OnWindowVisibilityChanged();
void OnScrolledToAnchor(); void OnScrolledToAnchor();
void OnAlertShown(); void OnAlertShown();
void RunPostponedEvents();
void ResendFocusSignalsForCurrentlyFocusedNode(); void ResendFocusSignalsForCurrentlyFocusedNode();
bool SupportsSelectionWithAtkSelection(); bool SupportsSelectionWithAtkSelection();
...@@ -426,6 +427,8 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase { ...@@ -426,6 +427,8 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
// The default ATK text attributes for this node. // The default ATK text attributes for this node.
TextAttributeList default_text_attributes_; TextAttributeList default_text_attributes_;
bool window_activate_event_postponed_ = false;
DISALLOW_COPY_AND_ASSIGN(AXPlatformNodeAuraLinux); DISALLOW_COPY_AND_ASSIGN(AXPlatformNodeAuraLinux);
}; };
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <vector> #include <vector>
#include "testing/gtest/include/gtest/gtest.h" #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_auralinux.h"
#include "ui/accessibility/platform/ax_platform_node_unittest.h" #include "ui/accessibility/platform/ax_platform_node_unittest.h"
#include "ui/accessibility/platform/test_ax_node_wrapper.h" #include "ui/accessibility/platform/test_ax_node_wrapper.h"
...@@ -1620,6 +1621,10 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkWindowActive) { ...@@ -1620,6 +1621,10 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkWindowActive) {
}), }),
&saw_active_focus_state_change); &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); ActivationTester tester(root_atk_object);
EXPECT_FALSE(tester.IsActivatedInStateSet()); EXPECT_FALSE(tester.IsActivatedInStateSet());
...@@ -1646,6 +1651,70 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkWindowActive) { ...@@ -1646,6 +1651,70 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkWindowActive) {
g_object_unref(root_atk_object); 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 // 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