Skip to content
Permalink
Browse files
[GTK][a11y] Unregister objects and clear cache when last client disco…
…nnect with ATSPI

https://bugs.webkit.org/show_bug.cgi?id=234781

Reviewed by Adrian Perez de Castro.

We currently register objects when there are clients asking for cached items or connect event listeners. We
could unregister the objects and clear the cache if all the clients are disconnected. This patch adds a low
priority timer to clear the cache and unregister the objects if there aren't new clients after 10 seconds
since the last client disconnected.

* accessibility/atspi/AccessibilityAtspi.cpp:
(WebCore::AccessibilityAtspi::initializeRegistry): Remove the calls to registerTrees(), since it's now done by
addClient() when the first client is added.
(WebCore::AccessibilityAtspi::addEventListener): Call addClient().
(WebCore::AccessibilityAtspi::addClient): Register the trees if this is the first client. Add the client to the
map and subscribe to NameOwnerChanged to remove the client when it's disconnected.
(WebCore::AccessibilityAtspi::removeClient): Remove the client and start the clear cache timer if the clients
map is empty.
(WebCore::AccessibilityAtspi::cacheClearTimerFired): Unregister all the objects and clear the cache.
(WebCore::AccessibilityAtspi::registerTrees const): Deleted.
* accessibility/atspi/AccessibilityAtspi.h:
(WebCore::AccessibilityAtspi::hasClients const): Return whether there are clients connected.
(WebCore::AccessibilityAtspi::hasEventListeners const): Deleted.
* accessibility/atspi/AccessibilityObjectAtspi.cpp:
(WebCore::AccessibilityObjectAtspi::didUnregisterObject): Object was unregistered by the clear cache timer,
reset the path and isRegistered members.
* accessibility/atspi/AccessibilityObjectAtspi.h:
* accessibility/atspi/AccessibilityRootAtspi.cpp:
(WebCore::AccessibilityRootAtspi::registerTree): Return early if already registered.
(WebCore::AccessibilityRootAtspi::didUnregisterTree): Tree was unregistered by the clear caceh timer, reset the
treeIsRegistered member.
(WebCore::AccessibilityRootAtspi::embedded): Check if there are clients.
* accessibility/atspi/AccessibilityRootAtspi.h:


Canonical link: https://commits.webkit.org/246039@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@288013 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
carlosgcampos committed Jan 14, 2022
1 parent 75a37c7 commit 0a48158bbf31a1ca1aacdff49d3a05d4dd7df143
Showing 7 changed files with 138 additions and 19 deletions.
@@ -1,3 +1,39 @@
2022-01-14 Carlos Garcia Campos <cgarcia@igalia.com>

[GTK][a11y] Unregister objects and clear cache when last client disconnect with ATSPI
https://bugs.webkit.org/show_bug.cgi?id=234781

Reviewed by Adrian Perez de Castro.

We currently register objects when there are clients asking for cached items or connect event listeners. We
could unregister the objects and clear the cache if all the clients are disconnected. This patch adds a low
priority timer to clear the cache and unregister the objects if there aren't new clients after 10 seconds
since the last client disconnected.

* accessibility/atspi/AccessibilityAtspi.cpp:
(WebCore::AccessibilityAtspi::initializeRegistry): Remove the calls to registerTrees(), since it's now done by
addClient() when the first client is added.
(WebCore::AccessibilityAtspi::addEventListener): Call addClient().
(WebCore::AccessibilityAtspi::addClient): Register the trees if this is the first client. Add the client to the
map and subscribe to NameOwnerChanged to remove the client when it's disconnected.
(WebCore::AccessibilityAtspi::removeClient): Remove the client and start the clear cache timer if the clients
map is empty.
(WebCore::AccessibilityAtspi::cacheClearTimerFired): Unregister all the objects and clear the cache.
(WebCore::AccessibilityAtspi::registerTrees const): Deleted.
* accessibility/atspi/AccessibilityAtspi.h:
(WebCore::AccessibilityAtspi::hasClients const): Return whether there are clients connected.
(WebCore::AccessibilityAtspi::hasEventListeners const): Deleted.
* accessibility/atspi/AccessibilityObjectAtspi.cpp:
(WebCore::AccessibilityObjectAtspi::didUnregisterObject): Object was unregistered by the clear cache timer,
reset the path and isRegistered members.
* accessibility/atspi/AccessibilityObjectAtspi.h:
* accessibility/atspi/AccessibilityRootAtspi.cpp:
(WebCore::AccessibilityRootAtspi::registerTree): Return early if already registered.
(WebCore::AccessibilityRootAtspi::didUnregisterTree): Tree was unregistered by the clear caceh timer, reset the
treeIsRegistered member.
(WebCore::AccessibilityRootAtspi::embedded): Check if there are clients.
* accessibility/atspi/AccessibilityRootAtspi.h:

2022-01-14 Antti Koivisto <antti@apple.com>

[:has() pseudo-class] Avoid O(n^2) in style invalidation with repeated DOM mutations
@@ -29,6 +29,7 @@
#include <wtf/NeverDestroyed.h>
#include <wtf/SortedArrayMap.h>
#include <wtf/UUID.h>
#include <wtf/glib/RunLoopSourcePriority.h>

namespace WebCore {

@@ -63,15 +64,6 @@ void AccessibilityAtspi::connect(const String& busAddress)
});
}

void AccessibilityAtspi::registerTrees() const
{
RELEASE_ASSERT(!isMainThread());
for (auto* rootObject : m_rootObjects.keys()) {
if (!rootObject->isTreeRegistered())
rootObject->registerTree();
}
}

void AccessibilityAtspi::initializeRegistry()
{
RELEASE_ASSERT(!isMainThread());
@@ -90,7 +82,6 @@ void AccessibilityAtspi::initializeRegistry()
if (!g_strcmp0(signal, "EventListenerRegistered")) {
g_variant_get(parameters, "(&s&s@as)", &dbusName, &eventName, nullptr);
atspi->addEventListener(dbusName, eventName);
atspi->registerTrees();
} else if (!g_strcmp0(signal, "EventListenerDeregistered")) {
g_variant_get(parameters, "(&s&s)", &dbusName, &eventName);
atspi->removeEventListener(dbusName, eventName);
@@ -106,14 +97,11 @@ void AccessibilityAtspi::initializeRegistry()
GRefPtr<GVariant> events;
g_variant_get(result.get(), "(@a(ss))", &events.outPtr());
GVariantIter iter;
auto eventCount = g_variant_iter_init(&iter, events.get());
g_variant_iter_init(&iter, events.get());
const char* dbusName;
const char* eventName;
while (g_variant_iter_loop(&iter, "(&s&s)", &dbusName, &eventName))
addEventListener(dbusName, eventName);

if (eventCount)
registerTrees();
}

static GUniquePtr<char*> eventConvertingDetailToNonCamelCase(const char* eventName)
@@ -149,6 +137,7 @@ void AccessibilityAtspi::addEventListener(const char* dbusName, const char* even
return Vector<GUniquePtr<char*>> { };
}).iterator->value;
listeners.append(eventConvertingDetailToNonCamelCase(eventName));
addClient(dbusName);
}

static bool eventIsSubtype(char** needle, char** haystack)
@@ -184,6 +173,55 @@ void AccessibilityAtspi::removeEventListener(const char* dbusName, const char* e
m_eventListeners.remove(it);
}

void AccessibilityAtspi::addClient(const char* dbusName)
{
RELEASE_ASSERT(!isMainThread());
if (m_clients.isEmpty()) {
for (auto* rootObject : m_rootObjects.keys())
rootObject->registerTree();
}

auto addResult = m_clients.add(dbusName, 0);
if (!addResult.isNewEntry)
return;

m_cacheClearTimer = nullptr;

addResult.iterator->value = g_dbus_connection_signal_subscribe(m_connection.get(), nullptr, "org.freedesktop.DBus", "NameOwnerChanged", nullptr, dbusName,
G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE, [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant* parameters, gpointer userData) {
auto& atspi = *static_cast<AccessibilityAtspi*>(userData);
const char* interface;
const char* oldName;
const char* newName;
g_variant_get(parameters, "(&s&s&s)", &interface, &oldName, &newName);
if (*oldName != '\0' && *newName == '\0')
atspi.removeClient(oldName);
}, this, nullptr);
}

void AccessibilityAtspi::removeClient(const char* dbusName)
{
RELEASE_ASSERT(!isMainThread());
auto id = m_clients.take(dbusName);
if (!id)
return;

g_dbus_connection_signal_unsubscribe(m_connection.get(), id);

if (!m_clients.isEmpty())
return;

m_cacheUpdateList.clear();
m_cacheUpdateTimer->stop();

if (!m_cacheClearTimer) {
m_cacheClearTimer = makeUnique<RunLoop::Timer<AccessibilityAtspi>>(RunLoop::current(), this, &AccessibilityAtspi::cacheClearTimerFired);
m_cacheClearTimer->setPriority(RunLoopSourcePriority::ReleaseUnusedResourcesTimer);
}

m_cacheClearTimer->startOneShot(10_s);
}

bool AccessibilityAtspi::shouldEmitSignal(const char* interface, const char* name, const char* detail)
{
RELEASE_ASSERT(!isMainThread());
@@ -724,15 +762,15 @@ const char* AccessibilityAtspi::localizedRoleName(AccessibilityRole role)

GDBusInterfaceVTable AccessibilityAtspi::s_cacheFunctions = {
// method_call
[](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* methodName, GVariant*, GDBusMethodInvocation* invocation, gpointer userData) {
[](GDBusConnection*, const gchar* sender, const gchar*, const gchar*, const gchar* methodName, GVariant*, GDBusMethodInvocation* invocation, gpointer userData) {
RELEASE_ASSERT(!isMainThread());
if (!g_strcmp0(methodName, "GetItems")) {
auto& atspi = *static_cast<AccessibilityAtspi*>(userData);
atspi.addClient(sender);
GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("(" GET_ITEMS_SIGNATURE ")"));
g_variant_builder_open(&builder, G_VARIANT_TYPE(GET_ITEMS_SIGNATURE));
for (auto* rootObject : atspi.m_rootObjects.keys()) {
g_variant_builder_open(&builder, G_VARIANT_TYPE("(" ITEM_SIGNATURE ")"));
rootObject->registerTree();
rootObject->serialize(&builder);
g_variant_builder_close(&builder);
}
@@ -841,6 +879,31 @@ void AccessibilityAtspi::removeAccessible(AccessibilityObjectAtspi& atspiObject)
g_variant_new("((so))", uniqueName(), path.utf8().data()), nullptr);
}

void AccessibilityAtspi::cacheClearTimerFired()
{
for (const auto& registeredObjects : m_atspiHyperlinks.values()) {
for (auto id : registeredObjects)
g_dbus_connection_unregister_object(m_connection.get(), id);
}
m_atspiHyperlinks.clear();
for (const auto& it : m_atspiObjects) {
for (auto id : it.value)
g_dbus_connection_unregister_object(m_connection.get(), id);

it.key->didUnregisterObject();
}
m_atspiObjects.clear();

for (auto* rootObject : m_rootObjects.keys())
rootObject->didUnregisterTree();

m_cache.clear();

RELEASE_ASSERT(m_cacheUpdateList.isEmpty());
m_cacheUpdateTimer = nullptr;
m_cacheClearTimer = nullptr;
}

namespace Accessibility {

PlatformRoleMap createPlatformRoleMap()
@@ -53,7 +53,7 @@ class AccessibilityAtspi {
const char* uniqueName() const;
GVariant* nullReference() const;
GVariant* applicationReference() const;
bool hasEventListeners() const { return !m_eventListeners.isEmpty(); }
bool hasClients() const { return !m_clients.isEmpty(); }

void registerRoot(AccessibilityRootAtspi&, Vector<std::pair<GDBusInterfaceInfo*, GDBusInterfaceVTable*>>&&, CompletionHandler<void(const String&)>&&);
void unregisterRoot(AccessibilityRootAtspi&);
@@ -94,17 +94,19 @@ class AccessibilityAtspi {
private:
AccessibilityAtspi();

void registerTrees() const;
void initializeRegistry();
void addEventListener(const char* dbusName, const char* eventName);
void removeEventListener(const char* dbusName, const char* eventName);
void addClient(const char* dbusName);
void removeClient(const char* dbusName);

void ensureCache();
void addToCacheIfNeeded(AccessibilityObjectAtspi&);
void addToCacheIfPending(AccessibilityObjectAtspi&);
void removeAccessible(AccessibilityObjectAtspi&);
void scheduleCacheUpdate();
void cacheUpdateTimerFired();
void cacheClearTimerFired();

bool shouldEmitSignal(const char* interface, const char* name, const char* detail = "");

@@ -127,10 +129,12 @@ class AccessibilityAtspi {
HashMap<AccessibilityRootAtspi*, Vector<unsigned, 2>> m_rootObjects;
HashMap<AccessibilityObjectAtspi*, Vector<unsigned, 20>> m_atspiObjects;
HashMap<AccessibilityObjectAtspi*, Vector<unsigned, 20>> m_atspiHyperlinks;
HashMap<CString, unsigned> m_clients;
unsigned m_cacheID { 0 };
HashMap<String, AccessibilityObjectAtspi*> m_cache;
ListHashSet<RefPtr<AccessibilityObjectAtspi>> m_cacheUpdateList;
std::unique_ptr<RunLoop::Timer<AccessibilityAtspi>> m_cacheUpdateTimer;
std::unique_ptr<RunLoop::Timer<AccessibilityAtspi>> m_cacheClearTimer;
#if ENABLE(DEVELOPER_MODE)
HashMap<void*, NotificationObserver> m_notificationObservers;
#endif
@@ -541,6 +541,12 @@ bool AccessibilityObjectAtspi::registerObject()
return true;
}

void AccessibilityObjectAtspi::didUnregisterObject()
{
m_isRegistered.store(false);
m_path = { };
}

const String& AccessibilityObjectAtspi::path()
{
RELEASE_ASSERT(!isMainThread());
@@ -43,6 +43,7 @@ class AccessibilityObjectAtspi final : public ThreadSafeRefCounted<Accessibility

bool registerObject();
bool isTreeRegistered() const;
void didUnregisterObject();

enum class Interface : uint16_t {
Accessible = 1 << 0,
@@ -205,12 +205,20 @@ void AccessibilityRootAtspi::registerTree()
if (m_parentUniqueName.isNull())
return;

if (m_isTreeRegistered.load())
return;

registerSubtree(Accessibility::retrieveValueFromMainThread<AccessibilityObjectAtspi*>([this]() -> AccessibilityObjectAtspi* {
return child();
}));
m_isTreeRegistered.store(true);
}

void AccessibilityRootAtspi::didUnregisterTree()
{
m_isTreeRegistered.store(false);
}

void AccessibilityRootAtspi::setPath(String&& path)
{
RELEASE_ASSERT(!isMainThread());
@@ -223,7 +231,7 @@ void AccessibilityRootAtspi::embedded(const char* parentUniqueName, const char*
m_parentUniqueName = parentUniqueName;
m_parentPath = parentPath;
AccessibilityAtspi::singleton().parentChanged(*this);
if (!m_isTreeRegistered.load() && AccessibilityAtspi::singleton().hasEventListeners())
if (AccessibilityAtspi::singleton().hasClients())
registerTree();
}

@@ -42,6 +42,7 @@ class AccessibilityRootAtspi final : public ThreadSafeRefCounted<AccessibilityRo
void registerObject(CompletionHandler<void(const String&)>&&);
void unregisterObject();
void registerTree();
void didUnregisterTree();
bool isTreeRegistered() const { return m_isTreeRegistered.load(); }
void setPath(String&&);

0 comments on commit 0a48158

Please sign in to comment.