WebKit Bugzilla
New
Browse
Search+
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
[patch]
Patch
wcore-atspi-accessible.diff (text/plain), 128.26 KB, created by
Carlos Garcia Campos
on 2021-10-01 01:40:30 PDT
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Carlos Garcia Campos
Created:
2021-10-01 01:40:30 PDT
Size:
128.26 KB
patch
obsolete
>diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog >index 1026f82d8135..310e5e451119 100644 >--- a/Source/WebCore/ChangeLog >+++ b/Source/WebCore/ChangeLog >@@ -1,3 +1,85 @@ >+2021-09-30 Carlos Garcia Campos <cgarcia@igalia.com> >+ >+ [GTK][a11y] Add initial implementation of accessible interface when building with ATSPI >+ https://bugs.webkit.org/show_bug.cgi?id=230256 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * PlatformGTK.cmake: >+ * accessibility/AXObjectCache.h: >+ * accessibility/AccessibilityNodeObject.cpp: >+ (WebCore::AccessibilityNodeObject::canSetValueAttribute const): >+ * accessibility/AccessibilityObject.cpp: >+ (WebCore::AccessibilityObject::rolePlatformString const): >+ * accessibility/atspi/AXObjectCacheAtspi.cpp: >+ (WebCore::AXObjectCache::attachWrapper): >+ (WebCore::AXObjectCache::postPlatformNotification): >+ (WebCore::AXObjectCache::platformHandleFocusedUIElementChanged): >+ (WebCore::AXObjectCache::detachWrapper): Deleted. >+ * accessibility/atspi/AccessibilityAtspi.cpp: >+ (WebCore::AccessibilityAtspi::registerRoot): >+ (WebCore::AccessibilityAtspi::unregisterRoot): >+ (WebCore::AccessibilityAtspi::registerObject): >+ (WebCore::AccessibilityAtspi::unregisterObject): >+ (WebCore::AccessibilityAtspi::childrenChanged): >+ (WebCore::AccessibilityAtspi::stateChanged): >+ (WebCore::AccessibilityAtspi::localizedRoleName): >+ (WebCore::AccessibilityAtspi::ensureCache): >+ (WebCore::AccessibilityAtspi::addAccessible): >+ (WebCore::AccessibilityAtspi::removeAccessible): >+ (WebCore::Accessibility::createPlatformRoleMap): >+ * accessibility/atspi/AccessibilityAtspi.h: >+ * accessibility/atspi/AccessibilityAtspiEnums.h: Added. >+ * accessibility/atspi/AccessibilityObjectAtspi.cpp: >+ (WebCore::AccessibilityObjectAtspi::interfacesForObject): >+ (WebCore::AccessibilityObjectAtspi::AccessibilityObjectAtspi): >+ (WebCore::AccessibilityObjectAtspi::attach): >+ (WebCore::AccessibilityObjectAtspi::detach): >+ (WebCore::AccessibilityObjectAtspi::cacheDestroyed): >+ (WebCore::AccessibilityObjectAtspi::elementDestroyed): >+ (WebCore::atspiRole): >+ (WebCore::AccessibilityObjectAtspi::path): >+ (WebCore::AccessibilityObjectAtspi::reference): >+ (WebCore::AccessibilityObjectAtspi::setRoot): >+ (WebCore::AccessibilityObjectAtspi::root const): >+ (WebCore::AccessibilityObjectAtspi::setParent): >+ (WebCore:: const): >+ (WebCore::AccessibilityObjectAtspi::parentReference const): >+ (WebCore::AccessibilityObjectAtspi::childCount const): >+ (WebCore::AccessibilityObjectAtspi::childAt const): >+ (WebCore::AccessibilityObjectAtspi::children const): >+ (WebCore::AccessibilityObjectAtspi::indexInParent const): >+ (WebCore::AccessibilityObjectAtspi::indexInParentForChildrenChanged): >+ (WebCore::AccessibilityObjectAtspi::name const): >+ (WebCore::AccessibilityObjectAtspi::description const): >+ (WebCore::shouldIncludeOrientationState): >+ (WebCore::AccessibilityObjectAtspi::state const): >+ (WebCore::AccessibilityObjectAtspi::id const): >+ (WebCore::AccessibilityObjectAtspi::attributes const): >+ (WebCore::AccessibilityObjectAtspi::buildAttributes const): >+ (WebCore::AccessibilityObjectAtspi::relationMap const): >+ (WebCore::AccessibilityObjectAtspi::buildRelationSet const): >+ (WebCore::AccessibilityObjectAtspi::buildInterfaces const): >+ (WebCore::AccessibilityObjectAtspi::serialize const): >+ (WebCore::AccessibilityObjectAtspi::childAdded): >+ (WebCore::AccessibilityObjectAtspi::childRemoved): >+ (WebCore::AccessibilityObjectAtspi::stateChanged): >+ (WebCore::AccessibilityObjectAtspi::role const): >+ (WebCore::AccessibilityObjectAtspi::roleName const): >+ (WebCore::AccessibilityObjectAtspi::localizedRoleName const): >+ (WebCore::AccessibilityObjectAtspi::updateBackingStore): >+ (WebCore::AccessibilityObject::detachPlatformWrapper): >+ (WebCore::AccessibilityObject::accessibilityPlatformIncludesObject const): >+ * accessibility/atspi/AccessibilityObjectAtspi.h: >+ * accessibility/atspi/AccessibilityRootAtspi.cpp: >+ (WebCore::AccessibilityRootAtspi::unregisterObject): >+ (WebCore::AccessibilityRootAtspi::child const): >+ (WebCore::AccessibilityRootAtspi::serialize const): >+ * accessibility/atspi/AccessibilityRootAtspi.h: >+ * accessibility/isolatedtree/atspi/AXIsolatedObjectAtspi.cpp: >+ (WebCore::AXIsolatedObject::attachPlatformWrapper): >+ (WebCore::AXIsolatedObject::detachPlatformWrapper): >+ > 2021-09-30 Carlos Garcia Campos <cgarcia@igalia.com> > > Unreviewed GTK build fix after r283304 >diff --git a/Source/WebCore/PlatformGTK.cmake b/Source/WebCore/PlatformGTK.cmake >index 666a35b53a4b..4d439d4857b3 100644 >--- a/Source/WebCore/PlatformGTK.cmake >+++ b/Source/WebCore/PlatformGTK.cmake >@@ -55,6 +55,7 @@ endif () > > list(APPEND WebCore_PRIVATE_FRAMEWORK_HEADERS > accessibility/atspi/AccessibilityAtspi.h >+ accessibility/atspi/AccessibilityAtspiEnums.h > accessibility/atspi/AccessibilityObjectAtspi.h > accessibility/atspi/AccessibilityRootAtspi.h > >diff --git a/Source/WebCore/accessibility/AXObjectCache.h b/Source/WebCore/accessibility/AXObjectCache.h >index 439f6b4e79bd..f6a3f6230cbd 100644 >--- a/Source/WebCore/accessibility/AXObjectCache.h >+++ b/Source/WebCore/accessibility/AXObjectCache.h >@@ -172,7 +172,7 @@ public: > void remove(Widget*); > void remove(AXID); > >-#if !PLATFORM(COCOA) >+#if !PLATFORM(COCOA) && !USE(ATSPI) > void detachWrapper(AXCoreObject*, AccessibilityDetachmentType); > #endif > private: >@@ -584,7 +584,7 @@ inline void AXObjectCache::deferRecomputeIsIgnored(Element*) { } > inline void AXObjectCache::deferTextChangedIfNeeded(Node*) { } > inline void AXObjectCache::deferSelectedChildrenChangedIfNeeded(Element&) { } > inline void AXObjectCache::deferTextReplacementNotificationForTextControl(HTMLTextFormControlElement&, const String&) { } >-#if !PLATFORM(COCOA) >+#if !PLATFORM(COCOA) && !USE(ATSPI) > inline void AXObjectCache::detachWrapper(AXCoreObject*, AccessibilityDetachmentType) { } > #endif > inline void AXObjectCache::focusModalNodeTimerFired() { } >diff --git a/Source/WebCore/accessibility/AccessibilityNodeObject.cpp b/Source/WebCore/accessibility/AccessibilityNodeObject.cpp >index 270bbd9d8ca8..530253dfbc91 100644 >--- a/Source/WebCore/accessibility/AccessibilityNodeObject.cpp >+++ b/Source/WebCore/accessibility/AccessibilityNodeObject.cpp >@@ -2222,7 +2222,7 @@ bool AccessibilityNodeObject::canSetValueAttribute() const > if (isProgressIndicator() || isSlider() || isScrollbar()) > return true; > >-#if USE(ATK) >+#if USE(ATK) || USE(ATSPI) > // In ATK, input types which support aria-readonly are treated as having a > // settable value if the user can modify the widget's value or its state. > if (supportsReadOnly()) >diff --git a/Source/WebCore/accessibility/AccessibilityObject.cpp b/Source/WebCore/accessibility/AccessibilityObject.cpp >index 0f016c453265..50694a98d3aa 100644 >--- a/Source/WebCore/accessibility/AccessibilityObject.cpp >+++ b/Source/WebCore/accessibility/AccessibilityObject.cpp >@@ -2272,8 +2272,7 @@ bool AccessibilityObject::hasHighlighting() const > #if !PLATFORM(MAC) > String AccessibilityObject::rolePlatformString() const > { >- // FIXME: implement in other platforms. >- return String(); >+ return Accessibility::roleToPlatformString(roleValue()); > } > > String AccessibilityObject::rolePlatformDescription() const >@@ -3649,7 +3648,7 @@ String AccessibilityObject::outerHTML() const > > namespace Accessibility { > >-#if !PLATFORM(MAC) >+#if !PLATFORM(MAC) && !USE(ATSPI) > // FIXME: implement in other platforms. > PlatformRoleMap createPlatformRoleMap() { return PlatformRoleMap(); } > #endif >diff --git a/Source/WebCore/accessibility/atspi/AXObjectCacheAtspi.cpp b/Source/WebCore/accessibility/atspi/AXObjectCacheAtspi.cpp >index 6be4c399f8de..4a476d7827b7 100644 >--- a/Source/WebCore/accessibility/atspi/AXObjectCacheAtspi.cpp >+++ b/Source/WebCore/accessibility/atspi/AXObjectCacheAtspi.cpp >@@ -24,16 +24,28 @@ > #include "AccessibilityObject.h" > #include "AccessibilityObjectAtspi.h" > #include "AccessibilityRenderObject.h" >-#include "Node.h" >+#include "Document.h" >+#include "Element.h" >+#include "HTMLSelectElement.h" >+#include "Range.h" >+#include "TextIterator.h" > > namespace WebCore { > >-void AXObjectCache::detachWrapper(AXCoreObject* axObject, AccessibilityDetachmentType detachmentType) >-{ >-} >- > void AXObjectCache::attachWrapper(AXCoreObject* axObject) > { >+ auto wrapper = AccessibilityObjectAtspi::create(axObject); >+ axObject->setWrapper(wrapper.ptr()); >+ >+ auto* axParent = axObject->parentObjectUnignored(); >+ if (!axParent) >+ return; >+ >+ auto* axParentWrapper = axParent->wrapper(); >+ if (!axParentWrapper) >+ return; >+ >+ wrapper->setParent(axParentWrapper); > } > > void AXObjectCache::platformPerformDeferredCacheUpdate() >@@ -56,6 +68,121 @@ bool AXObjectCache::usedOnAXThread() > > void AXObjectCache::postPlatformNotification(AXCoreObject* coreObject, AXNotification notification) > { >+ RELEASE_ASSERT(isMainThread()); >+ auto* wrapper = coreObject->wrapper(); >+ if (!wrapper) >+ return; >+ >+ switch (notification) { >+ case AXCheckedStateChanged: >+ if (coreObject->isCheckboxOrRadio() || coreObject->isSwitch()) >+ wrapper->stateChanged("checked", coreObject->isChecked()); >+ break; >+ case AXSelectedStateChanged: >+ wrapper->stateChanged("selected", coreObject->isSelected()); >+ break; >+ case AXSelectedChildrenChanged: >+ case AXMenuListValueChanged: >+ break; >+ case AXValueChanged: >+ break; >+ case AXInvalidStatusChanged: >+ wrapper->stateChanged("invalid-entry", coreObject->invalidStatus() != "false"); >+ break; >+ case AXElementBusyChanged: >+ wrapper->stateChanged("busy", coreObject->isBusy()); >+ break; >+ case AXCurrentStateChanged: >+ break; >+ case AXRowExpanded: >+ wrapper->stateChanged("expanded", true); >+ break; >+ case AXRowCollapsed: >+ wrapper->stateChanged("expanded", false); >+ break; >+ case AXExpandedChanged: >+ wrapper->stateChanged("expanded", coreObject->isExpanded()); >+ break; >+ case AXDisabledStateChanged: { >+ bool enabledState = coreObject->isEnabled(); >+ wrapper->stateChanged("enabled", enabledState); >+ wrapper->stateChanged("sensitive", enabledState); >+ break; >+ } >+ case AXPressedStateChanged: >+ wrapper->stateChanged("pressed", coreObject->isPressed()); >+ break; >+ case AXReadOnlyStatusChanged: >+ wrapper->stateChanged("read-only", coreObject->canSetValueAttribute()); >+ break; >+ case AXRequiredStatusChanged: >+ wrapper->stateChanged("required", coreObject->isRequired()); >+ break; >+ case AXActiveDescendantChanged: >+ if (auto* descendant = coreObject->activeDescendant()) >+ platformHandleFocusedUIElementChanged(nullptr, descendant->node()); >+ break; >+ case AXAriaAttributeChanged: >+ break; >+ case AXAriaRoleChanged: >+ break; >+ case AXAutocorrectionOccured: >+ break; >+ case AXChildrenChanged: >+ break; >+ case AXFocusedUIElementChanged: >+ break; >+ case AXFrameLoadComplete: >+ break; >+ case AXIdAttributeChanged: >+ break; >+ case AXImageOverlayChanged: >+ break; >+ case AXLanguageChanged: >+ break; >+ case AXLayoutComplete: >+ break; >+ case AXLoadComplete: >+ break; >+ case AXNewDocumentLoadComplete: >+ break; >+ case AXPageScrolled: >+ break; >+ case AXSelectedTextChanged: >+ break; >+ case AXScrolledToAnchor: >+ break; >+ case AXLiveRegionCreated: >+ break; >+ case AXLiveRegionChanged: >+ break; >+ case AXMenuListItemSelected: >+ break; >+ case AXMenuClosed: >+ break; >+ case AXMenuOpened: >+ break; >+ case AXRowCountChanged: >+ break; >+ case AXPressDidSucceed: >+ break; >+ case AXPressDidFail: >+ break; >+ case AXSortDirectionChanged: >+ break; >+ case AXTextChanged: >+ break; >+ case AXDraggingStarted: >+ break; >+ case AXDraggingEnded: >+ break; >+ case AXDraggingEnteredDropZone: >+ break; >+ case AXDraggingDropped: >+ break; >+ case AXDraggingExitedDropZone: >+ break; >+ } > } > > void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject* object, AXTextChange textChange, unsigned offset, const String& text) >@@ -68,6 +195,14 @@ void AXObjectCache::frameLoadingEventPlatformNotification(AccessibilityObject* o > > void AXObjectCache::platformHandleFocusedUIElementChanged(Node* oldFocusedNode, Node* newFocusedNode) > { >+ if (auto* axObject = get(oldFocusedNode)) { >+ if (auto* wrapper = axObject->wrapper()) >+ wrapper->stateChanged("focused", false); >+ } >+ if (auto* axObject = getOrCreate(newFocusedNode)) { >+ if (auto* wrapper = axObject->wrapper()) >+ wrapper->stateChanged("focused", true); >+ } > } > > void AXObjectCache::handleScrolledToAnchor(const Node*) >diff --git a/Source/WebCore/accessibility/atspi/AccessibilityAtspi.cpp b/Source/WebCore/accessibility/atspi/AccessibilityAtspi.cpp >index cafc81b5e1a4..6f449c1114c8 100644 >--- a/Source/WebCore/accessibility/atspi/AccessibilityAtspi.cpp >+++ b/Source/WebCore/accessibility/atspi/AccessibilityAtspi.cpp >@@ -26,6 +26,7 @@ > #include <gio/gio.h> > #include <glib/gi18n-lib.h> > #include <wtf/MainThread.h> >+#include <wtf/SortedArrayMap.h> > #include <wtf/UUID.h> > #include <wtf/glib/GUniquePtr.h> > >@@ -65,6 +66,7 @@ void AccessibilityAtspi::registerRoot(AccessibilityRootAtspi& rootObject, Vector > m_queue->dispatch([this, rootObject = Ref { rootObject }, interfaces = WTFMove(interfaces), completionHandler = WTFMove(completionHandler)]() mutable { > String reference; > if (m_connection) { >+ ensureCache(); > String path = makeString("/org/a11y/webkit/accessible/", createCanonicalUUIDString().replace('-', '_')); > Vector<unsigned, 2> registeredObjects; > registeredObjects.reserveInitialCapacity(interfaces.size()); >@@ -82,6 +84,323 @@ void AccessibilityAtspi::registerRoot(AccessibilityRootAtspi& rootObject, Vector > }); > } > >+void AccessibilityAtspi::unregisterRoot(AccessibilityRootAtspi& rootObject) >+{ >+ RELEASE_ASSERT(isMainThread()); >+ m_queue->dispatch([this, rootObject = Ref { rootObject }] { >+ if (!m_connection) >+ return; >+ >+ g_dbus_connection_emit_signal(m_connection.get(), nullptr, rootObject->path().utf8().data(), "org.a11y.atspi.Event.Object", "StateChanged", >+ g_variant_new("(siiva{sv})", "defunct", TRUE, 0, g_variant_new_string("0"), nullptr), nullptr); >+ >+ auto registeredObjects = m_rootObjects.take(rootObject.ptr()); >+ g_dbus_connection_emit_signal(m_connection.get(), nullptr, "/org/a11y/atspi/cache", "org.a11y.atspi.Cache", "RemoveAccessible", >+ g_variant_new("((so))", uniqueName(), rootObject->path().utf8().data()), nullptr); >+ for (auto id : registeredObjects) >+ g_dbus_connection_unregister_object(m_connection.get(), id); >+ }); >+} >+ >+String AccessibilityAtspi::registerObject(AccessibilityObjectAtspi& atspiObject, Vector<std::pair<GDBusInterfaceInfo*, GDBusInterfaceVTable*>>&& interfaces) >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ if (!m_connection) >+ return { }; >+ >+ ensureCache(); >+ String path = makeString("/org/a11y/atspi/accessible/", createCanonicalUUIDString().replace('-', '_')); >+ Vector<unsigned, 20> registeredObjects; >+ registeredObjects.reserveInitialCapacity(interfaces.size()); >+ for (const auto& interface : interfaces) { >+ auto id = g_dbus_connection_register_object(m_connection.get(), path.utf8().data(), interface.first, interface.second, &atspiObject, nullptr, nullptr); >+ registeredObjects.uncheckedAppend(id); >+ } >+ m_atspiObjects.add(&atspiObject, WTFMove(registeredObjects)); >+ >+ addAccessible(atspiObject, path); >+ >+ return path; >+} >+ >+void AccessibilityAtspi::unregisterObject(AccessibilityObjectAtspi& atspiObject) >+{ >+ RELEASE_ASSERT(isMainThread()); >+ m_queue->dispatch([this, atspiObject = Ref { atspiObject }] { >+ if (!m_connection) >+ return; >+ >+ g_dbus_connection_emit_signal(m_connection.get(), nullptr, atspiObject->path().utf8().data(), "org.a11y.atspi.Event.Object", "StateChanged", >+ g_variant_new("(siiva{sv})", "defunct", TRUE, 0, g_variant_new_string("0"), nullptr), nullptr); >+ >+ removeAccessible(atspiObject); >+ >+ auto registeredObjects = m_atspiObjects.take(atspiObject.ptr()); >+ for (auto id : registeredObjects) >+ g_dbus_connection_unregister_object(m_connection.get(), id); >+ }); >+} >+ >+void AccessibilityAtspi::childrenChanged(AccessibilityObjectAtspi& atspiObject, AccessibilityObjectAtspi& child, ChildrenChanged change) >+{ >+ RELEASE_ASSERT(isMainThread()); >+ m_queue->dispatch([this, atspiObject = Ref { atspiObject }, child = Ref { child }, change] { >+ if (!m_connection) >+ return; >+ >+ child->setRoot(atspiObject->root()); >+ g_dbus_connection_emit_signal(m_connection.get(), nullptr, atspiObject->path().utf8().data(), "org.a11y.atspi.Event.Object", "ChildrenChanged", >+ g_variant_new("(siiv(so))", change == ChildrenChanged::Added ? "add" : "remove", child->indexInParentForChildrenChanged(change), >+ 0, g_variant_new("(so)", uniqueName(), child->path().utf8().data()), uniqueName(), atspiObject->path().utf8().data()), nullptr); >+ }); >+} >+ >+void AccessibilityAtspi::stateChanged(AccessibilityObjectAtspi& atspiObject, const char* name, bool value) >+{ >+ RELEASE_ASSERT(isMainThread()); >+ m_queue->dispatch([this, atspiObject = Ref { atspiObject }, name = CString(name), value] { >+ if (!m_connection) >+ return; >+ >+ g_dbus_connection_emit_signal(m_connection.get(), nullptr, atspiObject->path().utf8().data(), "org.a11y.atspi.Event.Object", "StateChanged", >+ g_variant_new("(siiva{sv})", name.data(), value, 0, g_variant_new_string("0"), nullptr), nullptr); >+ }); >+} >+ >+struct RoleNameEntry { >+ const char* name; >+ const char* localizedName; >+}; >+ >+static constexpr std::pair<AccessibilityRole, RoleNameEntry> roleNames[] = { >+ { AccessibilityRole::Unknown, { "unknown", N_("unknown") } }, >+ { AccessibilityRole::Button, { "push button", N_("push button") } }, >+ { AccessibilityRole::RadioButton, { "radio button", N_("radio button") } }, >+ { AccessibilityRole::CheckBox, { "check box", N_("check box") } }, >+ { AccessibilityRole::Slider, { "slider", N_("slider") } }, >+ { AccessibilityRole::TabGroup, { "page tab list", N_("page tab list") } }, >+ { AccessibilityRole::TextField, { "entry", N_("entry") } }, >+ { AccessibilityRole::StaticText, { "text", N_("text") } }, >+ { AccessibilityRole::TextArea, { "entry", N_("entry") } }, >+ { AccessibilityRole::ScrollArea, { "scroll pane", N_("scroll pane") } }, >+ { AccessibilityRole::PopUpButton, { "combo box", N_("combo box") } }, >+ { AccessibilityRole::MenuButton, { "menu item", N_("menu item") } }, >+ { AccessibilityRole::Table, { "table", N_("table") } }, >+ { AccessibilityRole::Application, { "application", N_("application") } }, >+ { AccessibilityRole::Group, { "panel", N_("panel") } }, >+ { AccessibilityRole::TextGroup, { "section", N_("section") } }, >+ { AccessibilityRole::RadioGroup, { "panel", N_("panel") } }, >+ { AccessibilityRole::List, { "list", N_("list") } }, >+ { AccessibilityRole::Directory, { "directory pane", N_("directory pane") } }, >+ { AccessibilityRole::ScrollBar, { "scroll bar", N_("scroll bar") } }, >+ { AccessibilityRole::Image, { "image", N_("image") } }, >+ { AccessibilityRole::MenuBar, { "menu bar", N_("menu bar") } }, >+ { AccessibilityRole::Menu, { "menu", N_("menu") } }, >+ { AccessibilityRole::MenuItem, { "menu item", N_("menu item") } }, >+ { AccessibilityRole::MenuItemCheckbox, { "check menu item", N_("check menu item") } }, >+ { AccessibilityRole::MenuItemRadio, { "radio menu item", N_("radio menu item") } }, >+ { AccessibilityRole::Row, { "table row", N_("table row") } }, >+ { AccessibilityRole::Toolbar, { "tool bar", N_("tool bar") } }, >+ { AccessibilityRole::BusyIndicator, { "progress bar", N_("progress bar") } }, >+ { AccessibilityRole::ProgressIndicator, { "progress bar", N_("progress bar") } }, >+ { AccessibilityRole::Meter, { "level bar", N_("level bar") } }, >+ { AccessibilityRole::Window, { "window", N_("window") } }, >+ { AccessibilityRole::Outline, { "tree", N_("tree") } }, >+ { AccessibilityRole::ComboBox, { "combo box", N_("combo box") } }, >+ { AccessibilityRole::SplitGroup, { "split pane", N_("split pane") } }, >+ { AccessibilityRole::Splitter, { "separator", N_("separator") } }, >+ { AccessibilityRole::ColorWell, { "push button", N_("push button") } }, >+ { AccessibilityRole::Link, { "link", N_("link") } }, >+ { AccessibilityRole::Grid, { "table", N_("table") } }, >+ { AccessibilityRole::TreeGrid, { "tree table", N_("tree table") } }, >+ { AccessibilityRole::WebCoreLink, { "link", N_("link") } }, >+ { AccessibilityRole::ImageMapLink, { "link", N_("link") } }, >+ { AccessibilityRole::ImageMap, { "image map", N_("image map") } }, >+ { AccessibilityRole::ListMarker, { "text", N_("text") } }, >+ { AccessibilityRole::WebArea, { "document web", N_("document web") } }, >+ { AccessibilityRole::Heading, { "heading", N_("heading") } }, >+ { AccessibilityRole::ListBox, { "heading", N_("list box") } }, >+ { AccessibilityRole::ListBoxOption, { "list item", N_("list item") } }, >+ { AccessibilityRole::Cell, { "table cell", N_("table cell") } }, >+ { AccessibilityRole::GridCell, { "table cell", N_("table cell") } }, >+ { AccessibilityRole::ColumnHeader, { "column header", N_("column header") } }, >+ { AccessibilityRole::RowHeader, { "row header", N_("row header") } }, >+ { AccessibilityRole::Definition, { "definition", N_("definition") } }, >+ { AccessibilityRole::DescriptionListDetail, { "description value", N_("description value") } }, >+ { AccessibilityRole::DescriptionListTerm, { "description term", N_("description term") } }, >+ { AccessibilityRole::Term, { "description term", N_("description term") } }, >+ { AccessibilityRole::DescriptionList, { "description list", N_("description list") } }, >+ { AccessibilityRole::LandmarkBanner, { "landmark", N_("landmark") } }, >+ { AccessibilityRole::LandmarkComplementary, { "landmark", N_("landmark") } }, >+ { AccessibilityRole::LandmarkDocRegion, { "landmark", N_("landmark") } }, >+ { AccessibilityRole::LandmarkContentInfo, { "landmark", N_("landmark") } }, >+ { AccessibilityRole::LandmarkMain, { "landmark", N_("landmark") } }, >+ { AccessibilityRole::LandmarkNavigation, { "landmark", N_("landmark") } }, >+ { AccessibilityRole::LandmarkRegion, { "landmark", N_("landmark") } }, >+ { AccessibilityRole::LandmarkSearch, { "landmark", N_("landmark") } }, >+ { AccessibilityRole::ApplicationAlert, { "notification", N_("notification") } }, >+ { AccessibilityRole::ApplicationAlertDialog, { "alert", N_("alert") } }, >+ { AccessibilityRole::ApplicationDialog, { "dialog", N_("dialog") } }, >+ { AccessibilityRole::ApplicationGroup, { "grouping", N_("grouping") } }, >+ { AccessibilityRole::ApplicationTextGroup, { "section", N_("section") } }, >+ { AccessibilityRole::ApplicationLog, { "log", N_("log") } }, >+ { AccessibilityRole::ApplicationMarquee, { "marquee", N_("marquee") } }, >+ { AccessibilityRole::ApplicationStatus, { "statusbar", N_("statusbar") } }, >+ { AccessibilityRole::ApplicationTimer, { "timer", N_("timer") } }, >+ { AccessibilityRole::Document, { "document frame", N_("document frame") } }, >+ { AccessibilityRole::DocumentArticle, { "article", N_("article") } }, >+ { AccessibilityRole::DocumentMath, { "math", N_("math") } }, >+ { AccessibilityRole::DocumentNote, { "comment", N_("comment") } }, >+ { AccessibilityRole::UserInterfaceTooltip, { "tool tip", N_("tool tip") } }, >+ { AccessibilityRole::Tab, { "page tab", N_("page tab") } }, >+ { AccessibilityRole::TabList, { "page tab list", N_("page tab list") } }, >+ { AccessibilityRole::TabPanel, { "scroll pane", N_("scroll pane") } }, >+ { AccessibilityRole::Tree, { "tree", N_("tree") } }, >+ { AccessibilityRole::TreeItem, { "tree item", N_("tree item") } }, >+ { AccessibilityRole::ListItem, { "list item", N_("list item") } }, >+ { AccessibilityRole::Paragraph, { "paragraph", N_("paragraph") } }, >+ { AccessibilityRole::Label, { "label", N_("label") } }, >+ { AccessibilityRole::Div, { "section", N_("section") } }, >+ { AccessibilityRole::Form, { "form", N_("form") } }, >+ { AccessibilityRole::SpinButton, { "spin button", N_("spin button") } }, >+ { AccessibilityRole::Footer, { "footer", N_("footer") } }, >+ { AccessibilityRole::ToggleButton, { "toggle button", N_("toggle button") } }, >+ { AccessibilityRole::Canvas, { "canvas", N_("canvas") } }, >+ { AccessibilityRole::SVGRoot, { "panel", N_("panel") } }, >+ { AccessibilityRole::Legend, { "label", N_("label") } }, >+ { AccessibilityRole::MathElement, { "math", N_("math") } }, >+ { AccessibilityRole::Audio, { "audio", N_("audio") } }, >+ { AccessibilityRole::Video, { "video", N_("video") } }, >+ { AccessibilityRole::HorizontalRule, { "separator", N_("separator") } }, >+ { AccessibilityRole::Blockquote, { "block quote", N_("block quote") } }, >+ { AccessibilityRole::Switch, { "toggle button", N_("toggle button") } }, >+ { AccessibilityRole::SearchField, { "entry", N_("entry") } }, >+ { AccessibilityRole::Pre, { "section", N_("section") } }, >+ { AccessibilityRole::SVGTextPath, { "text", N_("text") } }, >+ { AccessibilityRole::SVGText, { "section", N_("section") } }, >+ { AccessibilityRole::SVGTSpan, { "text", N_("text") } }, >+ { AccessibilityRole::Inline, { "text", N_("text") } }, >+ { AccessibilityRole::Mark, { "mar", N_("mark") } }, >+ { AccessibilityRole::Time, { "text", N_("text") } }, >+ { AccessibilityRole::Feed, { "panel", N_("panel") } }, >+ { AccessibilityRole::Figure, { "panel", N_("panel") } }, >+ { AccessibilityRole::Footnote, { "footnote", N_("footnote") } }, >+ { AccessibilityRole::GraphicsDocument, { "document frame", N_("document frame") } }, >+ { AccessibilityRole::GraphicsObject, { "panel", N_("panel") } }, >+ { AccessibilityRole::GraphicsSymbol, { "image", N_("image") } }, >+ { AccessibilityRole::Caption, { "caption", N_("caption") } }, >+ { AccessibilityRole::Deletion, { "content deletion", N_("content deletion") } }, >+ { AccessibilityRole::Insertion, { "content insertion", N_("content insertion") } }, >+ { AccessibilityRole::Subscript, { "subscript", N_("subscript") } }, >+ { AccessibilityRole::Superscript, { "superscript", N_("superscript") } } >+}; >+ >+const char* AccessibilityAtspi::localizedRoleName(AccessibilityRole role) >+{ >+ static constexpr SortedArrayMap roleNamesMap { roleNames }; >+ if (auto entry = roleNamesMap.tryGet(role)) >+ return entry->localizedName; >+ >+ return _("unknown"); >+} >+ >+#define ITEM_SIGNATURE "(so)(so)(so)iiassusau" >+#define GET_ITEMS_SIGNATURE "a(" ITEM_SIGNATURE ")" >+ >+GDBusInterfaceVTable AccessibilityAtspi::s_cacheFunctions = { >+ // method_call >+ [](GDBusConnection*, const gchar*, 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); >+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("(" GET_ITEMS_SIGNATURE ")")); >+ g_variant_builder_open(&builder, G_VARIANT_TYPE(GET_ITEMS_SIGNATURE)); >+ for (const auto* rootObject : atspi.m_rootObjects.keys()) { >+ g_variant_builder_open(&builder, G_VARIANT_TYPE("(" ITEM_SIGNATURE ")")); >+ rootObject->serialize(&builder); >+ g_variant_builder_close(&builder); >+ } >+ >+ // We need to call updateBackingStore() on every object before calling serialize() >+ // and updating the backing store can detach the object and remove it from the cache. >+ auto paths = copyToVector(atspi.m_cache.keys()); >+ for (const auto& path : paths) { >+ auto wrapper = atspi.m_cache.get(path); >+ wrapper->updateBackingStore(); >+ if (!atspi.m_cache.contains(path)) >+ continue; >+ g_variant_builder_open(&builder, G_VARIANT_TYPE("(" ITEM_SIGNATURE ")")); >+ wrapper->serialize(&builder); >+ g_variant_builder_close(&builder); >+ } >+ g_variant_builder_close(&builder); >+ g_dbus_method_invocation_return_value(invocation, g_variant_builder_end(&builder)); >+ } >+ }, >+ // get_property >+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* propertyName, GError** error, gpointer) -> GVariant* { >+ g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Unknown property '%s'", propertyName); >+ return nullptr; >+ }, >+ // set_property, >+ nullptr, >+ // padding >+ nullptr >+}; >+ >+void AccessibilityAtspi::ensureCache() >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ if (m_cacheID || !m_connection) >+ return; >+ >+ m_cacheID = g_dbus_connection_register_object(m_connection.get(), "/org/a11y/atspi/cache", const_cast<GDBusInterfaceInfo*>(&webkit_cache_interface), &s_cacheFunctions, this, nullptr, nullptr); >+} >+ >+void AccessibilityAtspi::addAccessible(AccessibilityObjectAtspi& atspiObject, const String& path) >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ if (!m_connection) >+ return; >+ >+ auto addResult = m_cache.add(path, &atspiObject); >+ if (!addResult.isNewEntry) >+ return; >+ >+ // AddAccessible needs to be emitted after ChildrenChanged. >+ RunLoop::current().dispatch([this, atspiObject = Ref { atspiObject }, path = path.isolatedCopy()] { >+ atspiObject->updateBackingStore(); >+ if (!m_cache.contains(path)) >+ return; >+ >+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("(" ITEM_SIGNATURE ")")); >+ atspiObject->serialize(&builder); >+ g_dbus_connection_emit_signal(m_connection.get(), nullptr, "/org/a11y/atspi/cache", "org.a11y.atspi.Cache", "AddAccessible", >+ g_variant_new("(@(" ITEM_SIGNATURE "))", g_variant_builder_end(&builder)), nullptr); >+ }); >+} >+ >+void AccessibilityAtspi::removeAccessible(AccessibilityObjectAtspi& atspiObject) >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ const auto& path = atspiObject.path(); >+ m_cache.remove(path); >+ g_dbus_connection_emit_signal(m_connection.get(), nullptr, "/org/a11y/atspi/cache", "org.a11y.atspi.Cache", "RemoveAccessible", >+ g_variant_new("((so))", uniqueName(), path.utf8().data()), nullptr); >+} >+ >+namespace Accessibility { >+ >+PlatformRoleMap createPlatformRoleMap() >+{ >+ PlatformRoleMap roleMap; >+ for (const auto& entry : roleNames) >+ roleMap.add(static_cast<unsigned>(entry.first), String::fromUTF8(entry.second.name)); >+ return roleMap; >+} >+ >+} // namespace Accessibility >+ > } // namespace WebCore > > #endif // ENABLE(ACCESSIBILITY) && USE(ATSPI) >diff --git a/Source/WebCore/accessibility/atspi/AccessibilityAtspi.h b/Source/WebCore/accessibility/atspi/AccessibilityAtspi.h >index 1f08efcea5a9..ce37f93e3831 100644 >--- a/Source/WebCore/accessibility/atspi/AccessibilityAtspi.h >+++ b/Source/WebCore/accessibility/atspi/AccessibilityAtspi.h >@@ -30,10 +30,12 @@ > typedef struct _GDBusConnection GDBusConnection; > typedef struct _GDBusInterfaceInfo GDBusInterfaceInfo; > typedef struct _GDBusInterfaceVTable GDBusInterfaceVTable; >+typedef struct _GVariant GVariant; > > namespace WebCore { > class AccessibilityObjectAtspi; > class AccessibilityRootAtspi; >+enum class AccessibilityRole; > > class AccessibilityAtspi { > WTF_MAKE_FAST_ALLOCATED; >@@ -45,11 +47,30 @@ public: > GVariant* nullReference() const; > > void registerRoot(AccessibilityRootAtspi&, Vector<std::pair<GDBusInterfaceInfo*, GDBusInterfaceVTable*>>&&, CompletionHandler<void(const String&)>&&); >+ void unregisterRoot(AccessibilityRootAtspi&); >+ String registerObject(AccessibilityObjectAtspi&, Vector<std::pair<GDBusInterfaceInfo*, GDBusInterfaceVTable*>>&&); >+ void unregisterObject(AccessibilityObjectAtspi&); >+ >+ enum class ChildrenChanged { Added, Removed }; >+ void childrenChanged(AccessibilityObjectAtspi&, AccessibilityObjectAtspi&, ChildrenChanged); >+ >+ void stateChanged(AccessibilityObjectAtspi&, const char*, bool); >+ >+ static const char* localizedRoleName(AccessibilityRole); > > private: >+ void ensureCache(); >+ void addAccessible(AccessibilityObjectAtspi&, const String&); >+ void removeAccessible(AccessibilityObjectAtspi&); >+ >+ static GDBusInterfaceVTable s_cacheFunctions; >+ > Ref<WorkQueue> m_queue; > GRefPtr<GDBusConnection> m_connection; > HashMap<AccessibilityRootAtspi*, Vector<unsigned, 2>> m_rootObjects; >+ HashMap<AccessibilityObjectAtspi*, Vector<unsigned, 20>> m_atspiObjects; >+ unsigned m_cacheID { 0 }; >+ HashMap<String, AccessibilityObjectAtspi*> m_cache; > }; > > } // namespace WebCore >diff --git a/Source/WebCore/accessibility/atspi/AccessibilityAtspiEnums.h b/Source/WebCore/accessibility/atspi/AccessibilityAtspiEnums.h >new file mode 100644 >index 000000000000..ea7627b9ee51 >--- /dev/null >+++ b/Source/WebCore/accessibility/atspi/AccessibilityAtspiEnums.h >@@ -0,0 +1,237 @@ >+/* >+ * Copyright (C) 2021 Igalia S.L. >+ * >+ * This library is free software; you can redistribute it and/or >+ * modify it under the terms of the GNU Library General Public >+ * License as published by the Free Software Foundation; either >+ * version 2 of the License, or (at your option) any later version. >+ * >+ * This library is distributed in the hope that it will be useful, >+ * but WITHOUT ANY WARRANTY; without even the implied warranty of >+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU >+ * Library General Public License for more details. >+ * >+ * You should have received a copy of the GNU Library General Public License >+ * along with this library; see the file COPYING.LIB. If not, write to >+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, >+ * Boston, MA 02110-1301, USA. >+ */ >+ >+#pragma once >+ >+#if ENABLE(ACCESSIBILITY) && USE(ATSPI) >+namespace WebCore { >+namespace Atspi { >+ >+enum Role { >+ InvalidRole, >+ AcceleratorLabel, >+ Alert, >+ Animation, >+ Arrow, >+ Calendar, >+ Canvas, >+ CheckBox, >+ CheckMenuItem, >+ ColorChooser, >+ ColumnHeader, >+ ComboBox, >+ DateEditor, >+ DesktopIcon, >+ DesktopFrame, >+ Dial, >+ Dialog, >+ DirectoryPane, >+ DrawingArea, >+ FileChooser, >+ Filler, >+ FocusTraversable, >+ FontChooser, >+ Frame, >+ GlassPane, >+ HtmlContainer, >+ Icon, >+ Image, >+ InternalFrame, >+ Label, >+ LayeredPane, >+ List, >+ ListItem, >+ Menu, >+ MenuBar, >+ MenuItem, >+ OptionPane, >+ PageTab, >+ PageTabList, >+ Panel, >+ PasswordText, >+ PopupMenu, >+ ProgressBar, >+ PushButton, >+ RadioButton, >+ RadioMenuItem, >+ RootPane, >+ RowHeader, >+ ScrollBar, >+ ScrollPane, >+ Separator, >+ Slider, >+ SpinButton, >+ SplitPane, >+ StatusBar, >+ Table, >+ TableCell, >+ TableColumnHeader, >+ TableRowHeader, >+ TearoffMenuItem, >+ Terminal, >+ Text, >+ ToggleButton, >+ ToolBar, >+ ToolTip, >+ Tree, >+ TreeTable, >+ Unknown, >+ Viewport, >+ Window, >+ Extended, >+ Header, >+ Footer, >+ Paragraph, >+ Ruler, >+ Application, >+ Autocomplete, >+ Editbar, >+ Embedded, >+ Entry, >+ Chart, >+ Caption, >+ DocumentFrame, >+ Heading, >+ Page, >+ Section, >+ RedundantObject, >+ Form, >+ Link, >+ InputMethodWindow, >+ TableRow, >+ TreeItem, >+ DocumentSpreadsheet, >+ DocumentPresentation, >+ DocumentText, >+ DocumentWeb, >+ DocumentEmail, >+ Comment, >+ ListBox, >+ Grouping, >+ ImageMap, >+ Notification, >+ InfoBar, >+ LevelBar, >+ TitleBar, >+ BlockQuote, >+ Audio, >+ Video, >+ Definition, >+ Article, >+ Landmark, >+ Log, >+ Marquee, >+ Math, >+ Rating, >+ Timer, >+ Static, >+ MathFraction, >+ MathRoot, >+ Subscript, >+ Superscript, >+ DescriptionList, >+ DescriptionTerm, >+ DescriptionValue, >+ Footnote, >+ ContentDeletion, >+ ContentInsertion, >+ Mark, >+ Suggestion, >+ LastDefinedRole, >+}; >+ >+enum State { >+ InvalidState, >+ Active, >+ Armed, >+ Busy, >+ Checked, >+ Collapsed, >+ Defunct, >+ Editable, >+ Enabled, >+ Expandable, >+ Expanded, >+ Focusable, >+ Focused, >+ HasTooltip, >+ Horizontal, >+ Iconified, >+ Modal, >+ MultiLine, >+ Multiselectable, >+ Opaque, >+ Pressed, >+ Resizable, >+ Selectable, >+ Selected, >+ Sensitive, >+ Showing, >+ SingleLine, >+ Stale, >+ Transient, >+ Vertical, >+ Visible, >+ ManagesDescendants, >+ Indeterminate, >+ Required, >+ Truncated, >+ Animated, >+ InvalidEntry, >+ SupportsAutocompletion, >+ SelectableText, >+ IsDefault, >+ Visited, >+ Checkable, >+ HasPopup, >+ ReadOnly, >+ LastDefinedState, >+}; >+ >+enum Relation { >+ Null, >+ LabelFor, >+ LabelledBy, >+ ControllerFor, >+ ControlledBy, >+ MemberOf, >+ TooltipFor, >+ NodeChildOf, >+ NodeParentOf, >+ ExtendedRelation, >+ FlowsTo, >+ FlowsFrom, >+ SubwindowOf, >+ Embeds, >+ EmbeddedBy, >+ PoupFor, >+ ParentWindowOf, >+ DescriptionFor, >+ DescribedBy, >+ Details, >+ DetailsFor, >+ ErrorMessage, >+ ErrorFor, >+ LastDefinedRelation, >+}; >+ >+} // namespace Atspi >+} // namespace WebCore >+ >+#endif // ENABLE(ACCESSIBILITY) && USE(ATSPI) >diff --git a/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp b/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp >index d58b8b066556..698174174424 100644 >--- a/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp >+++ b/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp >@@ -21,7 +21,15 @@ > #include "AccessibilityObjectAtspi.h" > > #if ENABLE(ACCESSIBILITY) && USE(ATSPI) >+#include "AccessibilityAtspiEnums.h" > #include "AccessibilityObjectInterface.h" >+#include "AccessibilityRootAtspi.h" >+#include "AccessibilityTableCell.h" >+#include "RenderAncestorIterator.h" >+#include "RenderBlock.h" >+#include <glib/gi18n-lib.h> >+#include <wtf/MainThread.h> >+#include <wtf/UUID.h> > > namespace WebCore { > >@@ -30,12 +38,1116 @@ Ref<AccessibilityObjectAtspi> AccessibilityObjectAtspi::create(AXCoreObject* cor > return adoptRef(*new AccessibilityObjectAtspi(coreObject)); > } > >-AccessibilityObjectAtspi::AccessibilityObjectAtspi(AXCoreObject*) >+OptionSet<AccessibilityObjectAtspi::Interface> AccessibilityObjectAtspi::interfacesForObject(AXCoreObject& coreObject) > { >+ OptionSet<Interface> interfaces = { Interface::Accessible }; >+ >+ return interfaces; >+} >+ >+AccessibilityObjectAtspi::AccessibilityObjectAtspi(AXCoreObject* coreObject) >+ : m_coreObject(coreObject) >+ , m_interfaces(interfacesForObject(*m_coreObject)) >+{ >+ RELEASE_ASSERT(isMainThread()); >+} >+ >+void AccessibilityObjectAtspi::attach(AXCoreObject* axObject) >+{ >+ RELEASE_ASSERT(is<AXIsolatedObject>(*axObject)); >+ m_axObject = axObject; >+} >+ >+void AccessibilityObjectAtspi::detach() >+{ >+ m_axObject = nullptr; >+} >+ >+void AccessibilityObjectAtspi::cacheDestroyed() >+{ >+ RELEASE_ASSERT(isMainThread()); >+ m_coreObject = nullptr; >+ if (!m_isRegistered.load()) >+ return; >+ >+ root()->atspi().unregisterObject(*this); >+} >+ >+void AccessibilityObjectAtspi::elementDestroyed() >+{ >+ RELEASE_ASSERT(isMainThread()); >+ m_coreObject = nullptr; >+ if (!m_isRegistered.load()) >+ return; >+ >+ if (m_parent && *m_parent) >+ m_parent.value()->childRemoved(*this); >+ >+ root()->atspi().unregisterObject(*this); >+} >+ >+static unsigned atspiRole(AccessibilityRole role) >+{ >+ switch (role) { >+ case AccessibilityRole::Annotation: >+ case AccessibilityRole::ApplicationAlert: >+ return Atspi::Role::Notification; >+ case AccessibilityRole::ApplicationAlertDialog: >+ return Atspi::Role::Alert; >+ case AccessibilityRole::ApplicationDialog: >+ return Atspi::Role::Dialog; >+ case AccessibilityRole::ApplicationStatus: >+ return Atspi::Role::StatusBar; >+ case AccessibilityRole::Unknown: >+ return Atspi::Role::Unknown; >+ case AccessibilityRole::Audio: >+ return Atspi::Role::Audio; >+ case AccessibilityRole::Video: >+ return Atspi::Role::Video; >+ case AccessibilityRole::Button: >+ return Atspi::Role::PushButton; >+ case AccessibilityRole::Switch: >+ case AccessibilityRole::ToggleButton: >+ return Atspi::Role::ToggleButton; >+ case AccessibilityRole::RadioButton: >+ return Atspi::Role::RadioButton; >+ case AccessibilityRole::CheckBox: >+ return Atspi::Role::CheckBox; >+ case AccessibilityRole::Slider: >+ return Atspi::Role::Slider; >+ case AccessibilityRole::TabGroup: >+ case AccessibilityRole::TabList: >+ return Atspi::Role::PageTabList; >+ case AccessibilityRole::TextField: >+ case AccessibilityRole::TextArea: >+ case AccessibilityRole::SearchField: >+ return Atspi::Role::Entry; >+ case AccessibilityRole::StaticText: >+ return Atspi::Role::Static; >+ case AccessibilityRole::Outline: >+ case AccessibilityRole::Tree: >+ return Atspi::Role::Tree; >+ case AccessibilityRole::TreeItem: >+ return Atspi::Role::TreeItem; >+ case AccessibilityRole::MenuBar: >+ return Atspi::Role::MenuBar; >+ case AccessibilityRole::MenuListPopup: >+ case AccessibilityRole::Menu: >+ return Atspi::Role::Menu; >+ case AccessibilityRole::MenuListOption: >+ case AccessibilityRole::MenuItem: >+ case AccessibilityRole::MenuButton: >+ return Atspi::Role::MenuItem; >+ case AccessibilityRole::MenuItemCheckbox: >+ return Atspi::Role::CheckMenuItem; >+ case AccessibilityRole::MenuItemRadio: >+ return Atspi::Role::RadioMenuItem; >+ case AccessibilityRole::Column: >+ return Atspi::Role::Unknown; // There's no TableColumn in atspi. >+ case AccessibilityRole::Row: >+ return Atspi::Role::TableRow; >+ case AccessibilityRole::Toolbar: >+ return Atspi::Role::ToolBar; >+ case AccessibilityRole::Meter: >+ return Atspi::Role::LevelBar; >+ case AccessibilityRole::BusyIndicator: >+ case AccessibilityRole::ProgressIndicator: >+ return Atspi::Role::ProgressBar; >+ case AccessibilityRole::Window: >+ return Atspi::Role::Window; >+ case AccessibilityRole::PopUpButton: >+ case AccessibilityRole::ComboBox: >+ return Atspi::Role::ComboBox; >+ case AccessibilityRole::SplitGroup: >+ return Atspi::Role::SplitPane; >+ case AccessibilityRole::Splitter: >+ return Atspi::Role::Separator; >+ case AccessibilityRole::ColorWell: >+ return Atspi::Role::PushButton; >+ case AccessibilityRole::List: >+ return Atspi::Role::List; >+ case AccessibilityRole::ScrollBar: >+ return Atspi::Role::ScrollBar; >+ case AccessibilityRole::ScrollArea: >+ case AccessibilityRole::TabPanel: >+ return Atspi::Role::ScrollPane; >+ case AccessibilityRole::Grid: >+ case AccessibilityRole::Table: >+ return Atspi::Role::Table; >+ case AccessibilityRole::TreeGrid: >+ return Atspi::Role::TreeTable; >+ case AccessibilityRole::Application: >+ return Atspi::Role::Application; >+ case AccessibilityRole::ApplicationGroup: >+ case AccessibilityRole::Feed: >+ case AccessibilityRole::Figure: >+ case AccessibilityRole::GraphicsObject: >+ case AccessibilityRole::Group: >+ case AccessibilityRole::RadioGroup: >+ case AccessibilityRole::SVGRoot: >+ return Atspi::Role::Panel; >+ case AccessibilityRole::RowHeader: >+ return Atspi::Role::RowHeader; >+ case AccessibilityRole::ColumnHeader: >+ return Atspi::Role::ColumnHeader; >+ case AccessibilityRole::Caption: >+ return Atspi::Role::Caption; >+ case AccessibilityRole::Cell: >+ case AccessibilityRole::GridCell: >+ return Atspi::Role::TableCell; >+ case AccessibilityRole::Link: >+ case AccessibilityRole::WebCoreLink: >+ case AccessibilityRole::ImageMapLink: >+ return Atspi::Role::Link; >+ case AccessibilityRole::ImageMap: >+ return Atspi::Role::ImageMap; >+ case AccessibilityRole::GraphicsSymbol: >+ case AccessibilityRole::Image: >+ return Atspi::Role::Image; >+ case AccessibilityRole::ListMarker: >+ return Atspi::Role::Text; >+ case AccessibilityRole::DocumentArticle: >+ return Atspi::Role::Article; >+ case AccessibilityRole::Document: >+ case AccessibilityRole::GraphicsDocument: >+ return Atspi::Role::DocumentFrame; >+ case AccessibilityRole::DocumentNote: >+ return Atspi::Role::Comment; >+ case AccessibilityRole::Heading: >+ return Atspi::Role::Heading; >+ case AccessibilityRole::ListBox: >+ return Atspi::Role::ListBox; >+ case AccessibilityRole::ListItem: >+ case AccessibilityRole::ListBoxOption: >+ return Atspi::Role::ListItem; >+ case AccessibilityRole::Paragraph: >+ return Atspi::Role::Paragraph; >+ case AccessibilityRole::Label: >+ case AccessibilityRole::Legend: >+ return Atspi::Role::Label; >+ case AccessibilityRole::Blockquote: >+ return Atspi::Role::BlockQuote; >+ case AccessibilityRole::Footnote: >+ return Atspi::Role::Footnote; >+ case AccessibilityRole::ApplicationTextGroup: >+ case AccessibilityRole::Div: >+ case AccessibilityRole::Pre: >+ case AccessibilityRole::SVGText: >+ case AccessibilityRole::TextGroup: >+ return Atspi::Role::Section; >+ case AccessibilityRole::Footer: >+ return Atspi::Role::Footer; >+ case AccessibilityRole::Form: >+ return Atspi::Role::Form; >+ case AccessibilityRole::Canvas: >+ return Atspi::Role::Canvas; >+ case AccessibilityRole::HorizontalRule: >+ return Atspi::Role::Separator; >+ case AccessibilityRole::SpinButton: >+ return Atspi::Role::SpinButton; >+ case AccessibilityRole::Tab: >+ return Atspi::Role::PageTab; >+ case AccessibilityRole::UserInterfaceTooltip: >+ return Atspi::Role::ToolTip; >+ case AccessibilityRole::WebArea: >+ return Atspi::Role::DocumentWeb; >+ case AccessibilityRole::WebApplication: >+ return Atspi::Role::Embedded; >+ case AccessibilityRole::ApplicationLog: >+ return Atspi::Role::Log; >+ case AccessibilityRole::ApplicationMarquee: >+ return Atspi::Role::Marquee; >+ case AccessibilityRole::ApplicationTimer: >+ return Atspi::Role::Timer; >+ case AccessibilityRole::Definition: >+ return Atspi::Role::Definition; >+ case AccessibilityRole::DocumentMath: >+ return Atspi::Role::Math; >+ case AccessibilityRole::MathElement: >+ return Atspi::Role::MathFraction; >+ case AccessibilityRole::LandmarkBanner: >+ case AccessibilityRole::LandmarkComplementary: >+ case AccessibilityRole::LandmarkContentInfo: >+ case AccessibilityRole::LandmarkDocRegion: >+ case AccessibilityRole::LandmarkMain: >+ case AccessibilityRole::LandmarkNavigation: >+ case AccessibilityRole::LandmarkRegion: >+ case AccessibilityRole::LandmarkSearch: >+ return Atspi::Role::Landmark; >+ case AccessibilityRole::DescriptionList: >+ return Atspi::Role::DescriptionList; >+ case AccessibilityRole::Term: >+ case AccessibilityRole::DescriptionListTerm: >+ return Atspi::Role::DescriptionTerm; >+ case AccessibilityRole::DescriptionListDetail: >+ return Atspi::Role::DescriptionValue; >+ case AccessibilityRole::Deletion: >+ return Atspi::Role::ContentDeletion; >+ case AccessibilityRole::Insertion: >+ return Atspi::Role::ContentInsertion; >+ case AccessibilityRole::Subscript: >+ return Atspi::Role::Subscript; >+ case AccessibilityRole::Superscript: >+ return Atspi::Role::Superscript; >+ case AccessibilityRole::Inline: >+ case AccessibilityRole::SVGTextPath: >+ case AccessibilityRole::SVGTSpan: >+ case AccessibilityRole::Time: >+ return Atspi::Role::Static; >+ case AccessibilityRole::Directory: >+ return Atspi::Role::DirectoryPane; >+ case AccessibilityRole::Mark: >+ return Atspi::Role::Mark; >+ case AccessibilityRole::Browser: >+ case AccessibilityRole::Details: >+ case AccessibilityRole::DisclosureTriangle: >+ case AccessibilityRole::Drawer: >+ case AccessibilityRole::EditableText: >+ case AccessibilityRole::GrowArea: >+ case AccessibilityRole::HelpTag: >+ case AccessibilityRole::Ignored: >+ case AccessibilityRole::Incrementor: >+ case AccessibilityRole::Matte: >+ case AccessibilityRole::Presentational: >+ case AccessibilityRole::RowGroup: >+ case AccessibilityRole::RubyBase: >+ case AccessibilityRole::RubyBlock: >+ case AccessibilityRole::RubyInline: >+ case AccessibilityRole::RubyRun: >+ case AccessibilityRole::RubyText: >+ case AccessibilityRole::Ruler: >+ case AccessibilityRole::RulerMarker: >+ case AccessibilityRole::Sheet: >+ case AccessibilityRole::SliderThumb: >+ case AccessibilityRole::SpinButtonPart: >+ case AccessibilityRole::Summary: >+ case AccessibilityRole::SystemWide: >+ case AccessibilityRole::TableHeaderContainer: >+ case AccessibilityRole::ValueIndicator: >+ return Atspi::Role::Unknown; >+ } >+ >+ RELEASE_ASSERT_NOT_REACHED(); >+} >+ >+GDBusInterfaceVTable AccessibilityObjectAtspi::s_accessibleFunctions = { >+ // method_call >+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) { >+ RELEASE_ASSERT(!isMainThread()); >+ auto atspiObject = Ref { *static_cast<AccessibilityObjectAtspi*>(userData) }; >+ atspiObject->updateBackingStore(); >+ >+ if (!g_strcmp0(methodName, "GetRole")) >+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(u)", atspiObject->role())); >+ else if (!g_strcmp0(methodName, "GetRoleName")) >+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", atspiObject->roleName().utf8().data())); >+ else if (!g_strcmp0(methodName, "GetLocalizedRoleName")) >+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", atspiObject->localizedRoleName())); >+ else if (!g_strcmp0(methodName, "GetState")) { >+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("(au)")); >+ >+ auto states = atspiObject->state(); >+ g_variant_builder_open(&builder, G_VARIANT_TYPE("au")); >+ g_variant_builder_add(&builder, "u", static_cast<uint32_t>(states & 0xffffffff)); >+ g_variant_builder_add(&builder, "u", static_cast<uint32_t>(states >> 32)); >+ g_variant_builder_close(&builder); >+ >+ g_dbus_method_invocation_return_value(invocation, g_variant_builder_end(&builder)); >+ } else if (!g_strcmp0(methodName, "GetAttributes")) { >+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("(a{ss})")); >+ >+ g_variant_builder_open(&builder, G_VARIANT_TYPE("a{ss}")); >+ atspiObject->buildAttributes(&builder); >+ g_variant_builder_close(&builder); >+ >+ g_dbus_method_invocation_return_value(invocation, g_variant_builder_end(&builder)); >+ } else if (!g_strcmp0(methodName, "GetApplication")) >+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(@(so))", atspiObject->root()->applicationReference())); >+ else if (!g_strcmp0(methodName, "GetChildAtIndex")) { >+ int index; >+ g_variant_get(parameters, "(i)", &index); >+ auto* wrapper = index >= 0 ? atspiObject->childAt(index) : nullptr; >+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(@(so))", wrapper ? wrapper->reference() : atspiObject->root()->atspi().nullReference())); >+ } else if (!g_strcmp0(methodName, "GetChildren")) { >+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("a(so)")); >+ for (const auto& wrapper : atspiObject->children()) >+ g_variant_builder_add(&builder, "@(so)", wrapper->reference()); >+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(so))", &builder)); >+ } else if (!g_strcmp0(methodName, "GetIndexInParent")) { >+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", atspiObject->indexInParent())); >+ } else if (!g_strcmp0(methodName, "GetRelationSet")) { >+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("a(ua(so))")); >+ atspiObject->buildRelationSet(&builder); >+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(ua(so)))", &builder)); >+ } else if (!g_strcmp0(methodName, "GetInterfaces")) { >+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("as")); >+ atspiObject->buildInterfaces(&builder); >+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(as)", &builder)); >+ } >+ }, >+ // get_property >+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* propertyName, GError** error, gpointer userData) -> GVariant* { >+ RELEASE_ASSERT(!isMainThread()); >+ auto atspiObject = Ref { *static_cast<AccessibilityObjectAtspi*>(userData) }; >+ atspiObject->updateBackingStore(); >+ >+ if (!g_strcmp0(propertyName, "Name")) >+ return g_variant_new_string(atspiObject->name().data()); >+ if (!g_strcmp0(propertyName, "Description")) >+ return g_variant_new_string(atspiObject->description().data()); >+ if (!g_strcmp0(propertyName, "Locale")) >+ return g_variant_new_string(setlocale(LC_MESSAGES, nullptr)); >+ if (!g_strcmp0(propertyName, "AccessibleId")) >+ return g_variant_new_string(atspiObject->m_axObject ? String::number(atspiObject->m_axObject->objectID()).utf8().data() : ""); >+ if (!g_strcmp0(propertyName, "Parent")) >+ return atspiObject->parentReference(); >+ if (!g_strcmp0(propertyName, "ChildCount")) >+ return g_variant_new_int32(atspiObject->childCount()); >+ >+ g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Unknown property '%s'", propertyName); >+ return nullptr; >+ }, >+ // set_property, >+ nullptr, >+ // padding >+ nullptr >+}; >+ >+const String& AccessibilityObjectAtspi::path() >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ if (m_path.isNull()) { >+ auto* atspiRoot = root(); >+ RELEASE_ASSERT(atspiRoot); >+ m_isRegistered.store(true); >+ >+ Vector<std::pair<GDBusInterfaceInfo*, GDBusInterfaceVTable*>> interfaces; >+ if (m_interfaces.contains(Interface::Accessible)) >+ interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_accessible_interface), &s_accessibleFunctions }); >+ m_path = atspiRoot->atspi().registerObject(*this, WTFMove(interfaces)); >+ } >+ >+ return m_path; >+} >+ >+GVariant* AccessibilityObjectAtspi::reference() >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ return g_variant_new("(so)", root()->atspi().uniqueName(), path().utf8().data()); >+} >+ >+void AccessibilityObjectAtspi::setRoot(AccessibilityRootAtspi* root) >+{ >+ Locker locker { m_rootLock }; >+ m_root = root; >+} >+ >+AccessibilityRootAtspi* AccessibilityObjectAtspi::root() const >+{ >+ Locker locker { m_rootLock }; >+ return m_root; >+} >+ >+void AccessibilityObjectAtspi::setParent(std::optional<AccessibilityObjectAtspi*> atspiParent) >+{ >+ RELEASE_ASSERT(isMainThread()); >+ if (m_parent == atspiParent) >+ return; >+ >+ m_parent = atspiParent; >+ if (m_parent && *m_parent) >+ m_parent.value()->childAdded(*this); >+} >+ >+std::optional<AccessibilityObjectAtspi*> AccessibilityObjectAtspi::parent() const >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ if (!m_axObject) >+ return std::nullopt; >+ >+ auto* axParent = m_axObject->parentObjectUnignored(); >+ if (!axParent) >+ return nullptr; >+ >+ if (auto* atspiParent = axParent->wrapper()) >+ return atspiParent; >+ >+ return std::nullopt; >+} >+ >+GVariant* AccessibilityObjectAtspi::parentReference() const >+{ >+ auto parentAtspi = parent(); >+ if (!parentAtspi) >+ return root()->atspi().nullReference(); >+ >+ if (!parentAtspi.value()) >+ return root()->reference(); >+ >+ return parentAtspi.value()->reference(); >+} >+ >+unsigned AccessibilityObjectAtspi::childCount() const >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ if (!m_axObject) >+ return 0; >+ >+ return m_axObject->children().size(); >+} >+ >+AccessibilityObjectAtspi* AccessibilityObjectAtspi::childAt(unsigned index) const >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ if (!m_axObject) >+ return nullptr; >+ >+ const auto& children = m_axObject->children(); >+ if (index >= children.size()) >+ return nullptr; >+ >+ auto* wrapper = children[index]->wrapper(); >+ wrapper->setRoot(root()); >+ return wrapper; >+} >+ >+Vector<RefPtr<AccessibilityObjectAtspi>> AccessibilityObjectAtspi::children() const >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ if (!m_axObject) >+ return { }; >+ >+ const auto& children = m_axObject->children(); >+ Vector<RefPtr<AccessibilityObjectAtspi>> wrappers; >+ wrappers.reserveInitialCapacity(children.size()); >+ auto* root = this->root(); >+ for (const auto& child : children) { >+ if (auto* wrapper = child->wrapper()) { >+ wrapper->setRoot(root); >+ wrappers.uncheckedAppend(wrapper); >+ } >+ } >+ return wrappers; >+} >+ >+int AccessibilityObjectAtspi::indexInParent() const >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ if (!m_axObject) { >+ m_indexInParent = -1; >+ return m_indexInParent; >+ } >+ >+ m_axObject->updateBackingStore(); >+ >+ auto* axParent = m_axObject->parentObjectUnignored(); >+ if (!axParent) { >+ m_indexInParent = 0; >+ return m_indexInParent; >+ } >+ >+ const auto& children = axParent->children(); >+ unsigned index = 0; >+ for (const auto& child : children) { >+ if (child.get() == m_axObject) { >+ m_indexInParent = index; >+ return m_indexInParent; >+ } >+ index++; >+ } >+ >+ m_indexInParent = -1; >+ return m_indexInParent; >+} >+ >+int AccessibilityObjectAtspi::indexInParentForChildrenChanged(AccessibilityAtspi::ChildrenChanged change) >+{ >+ if (change == AccessibilityAtspi::ChildrenChanged::Removed) >+ return m_indexInParent; >+ >+ updateBackingStore(); >+ return indexInParent(); >+} >+ >+CString AccessibilityObjectAtspi::name() const >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ if (!m_axObject) >+ return ""; >+ >+ Vector<AccessibilityText> textOrder; >+ m_axObject->accessibilityText(textOrder); >+ >+ for (const auto& text : textOrder) { >+ // FIXME: This check is here because AccessibilityNodeObject::titleElementText() >+ // appends an empty String for the LabelByElementText source when there is a >+ // titleUIElement(). Removing this check makes some fieldsets lose their name. >+ if (text.text.isEmpty()) >+ continue; >+ >+ // WebCore Accessibility should provide us with the text alternative computation >+ // in the order defined by that spec. So take the first thing that our platform >+ // does not expose via the AtkObject description. >+ if (text.textSource != AccessibilityTextSource::Help && text.textSource != AccessibilityTextSource::Summary) >+ return text.text.utf8(); >+ } >+ >+ return ""; >+} >+ >+CString AccessibilityObjectAtspi::description() const >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ if (!m_axObject) >+ return ""; >+ >+ Vector<AccessibilityText> textOrder; >+ m_axObject->accessibilityText(textOrder); >+ >+ bool nameTextAvailable = false; >+ for (const auto& text : textOrder) { >+ // WebCore Accessibility should provide us with the text alternative computation >+ // in the order defined by that spec. So take the first thing that our platform >+ // does not expose via the AtkObject name. >+ if (text.textSource == AccessibilityTextSource::Help || text.textSource == AccessibilityTextSource::Summary) >+ return text.text.utf8(); >+ >+ // If there is no other text alternative, the title tag contents will have been >+ // used for the AtkObject name. We don't want to duplicate it here. >+ if (text.textSource == AccessibilityTextSource::TitleTag && nameTextAvailable) >+ return text.text.utf8(); >+ >+ nameTextAvailable = true; >+ } >+ >+ return ""; >+} >+ >+static bool shouldIncludeOrientationState(const AXCoreObject& coreObject) >+{ >+ return coreObject.isComboBox() >+ || coreObject.isRadioGroup() >+ || coreObject.isTreeGrid() >+ || coreObject.isScrollbar() >+ || coreObject.isListBox() >+ || coreObject.isMenu() >+ || coreObject.isTree() >+ || coreObject.isMenuBar() >+ || coreObject.isSplitter() >+ || coreObject.isTabList() >+ || coreObject.isToolbar() >+ || coreObject.isSlider(); >+} >+ >+uint64_t AccessibilityObjectAtspi::state() const >+{ >+ return Accessibility::retrieveValueFromMainThread<uint64_t>([this]() -> uint64_t { >+ uint64_t states = 0; >+ >+ auto addState = [&](Atspi::State atspiState) { >+ states |= (G_GUINT64_CONSTANT(1) << atspiState); >+ }; >+ >+ if (m_coreObject) >+ m_coreObject->updateBackingStore(); >+ >+ if (!m_coreObject) { >+ addState(Atspi::State::Defunct); >+ return states; >+ } >+ >+ if (m_coreObject->isEnabled()) { >+ addState(Atspi::State::Enabled); >+ addState(Atspi::State::Sensitive); >+ } >+ >+ if (m_coreObject->isVisible()) { >+ addState(Atspi::State::Visible); >+ if (!m_coreObject->isOffScreen()) >+ addState(Atspi::State::Showing); >+ } >+ >+ if (m_coreObject->canSetFocusAttribute()) >+ addState(Atspi::State::Focusable); >+ >+ if (m_coreObject->isFocused()) >+ addState(Atspi::State::Focused); >+ >+ if (m_coreObject->canSetValueAttribute()) { >+ if (m_coreObject->supportsChecked()) >+ addState(Atspi::State::Checkable); >+ >+ if (m_coreObject->isTextControl() || m_coreObject->isNonNativeTextControl()) >+ addState(Atspi::State::Editable); >+ } else if (m_coreObject->supportsReadOnly()) >+ addState(Atspi::State::ReadOnly); >+ >+ if (m_coreObject->isChecked()) >+ addState(Atspi::State::Checked); >+ >+ if (m_coreObject->isPressed()) >+ addState(Atspi::State::Pressed); >+ >+ if (m_coreObject->isRequired()) >+ addState(Atspi::State::Required); >+ >+ if (m_coreObject->roleValue() == AccessibilityRole::TextArea || m_coreObject->ariaIsMultiline()) >+ addState(Atspi::State::MultiLine); >+ else if (m_coreObject->roleValue() == AccessibilityRole::TextField || m_coreObject->roleValue() == AccessibilityRole::SearchField) >+ addState(Atspi::State::SingleLine); >+ >+ if (m_coreObject->isTextControl()) >+ addState(Atspi::State::SelectableText); >+ >+ if (m_coreObject->canSetSelectedAttribute()) >+ addState(Atspi::State::Selectable); >+ >+ if (m_coreObject->isMultiSelectable()) >+ addState(Atspi::State::Multiselectable); >+ >+ if (m_coreObject->isSelected()) >+ addState(Atspi::State::Selected); >+ >+ if (m_coreObject->canSetExpandedAttribute()) >+ addState(Atspi::State::Expandable); >+ >+ if (m_coreObject->isExpanded()) >+ addState(Atspi::State::Expanded); >+ >+ if (m_coreObject->hasPopup()) >+ addState(Atspi::State::HasPopup); >+ >+ if (shouldIncludeOrientationState(*m_coreObject)) { >+ switch (m_coreObject->orientation()) { >+ case AccessibilityOrientation::Horizontal: >+ addState(Atspi::State::Horizontal); >+ break; >+ case AccessibilityOrientation::Vertical: >+ addState(Atspi::State::Vertical); >+ break; >+ case AccessibilityOrientation::Undefined: >+ break; >+ } >+ } >+ >+ if (m_coreObject->isIndeterminate()) >+ addState(Atspi::State::Indeterminate); >+ else if ((m_coreObject->isCheckboxOrRadio() || m_coreObject->isMenuItem() || m_coreObject->isToggleButton()) && m_coreObject->checkboxOrRadioValue() == AccessibilityButtonState::Mixed) >+ addState(Atspi::State::Indeterminate); >+ >+ if (m_coreObject->isModalNode()) >+ addState(Atspi::State::Modal); >+ >+ if (m_coreObject->isBusy()) >+ addState(Atspi::State::Busy); >+ >+ if (m_coreObject->invalidStatus() != "false") >+ addState(Atspi::State::InvalidEntry); >+ >+ if (m_coreObject->supportsAutoComplete() && m_coreObject->autoCompleteValue() != "none") >+ addState(Atspi::State::SupportsAutocompletion); >+ >+ return states; >+ }); >+} >+ >+String AccessibilityObjectAtspi::id() const >+{ >+ RELEASE_ASSERT(isMainThread()); >+ if (m_coreObject) >+ m_coreObject->updateBackingStore(); >+ >+ if (!m_coreObject) >+ return { }; >+ >+ if (auto* element = m_coreObject->element()) >+ return element->getIdAttribute().string(); >+ >+ return { }; >+} >+ >+HashMap<String, String> AccessibilityObjectAtspi::attributes() const >+{ >+ RELEASE_ASSERT(isMainThread()); >+ HashMap<String, String> map; >+#if PLATFORM(GTK) >+ map.add("toolkit", "WebKitGTK"); >+#elif PLATFORM(WPE) >+ map.add("toolkit", "WPEWebKit"); >+#endif >+ if (m_coreObject) >+ m_coreObject->updateBackingStore(); >+ >+ if (!m_coreObject) >+ return map; >+ >+ String tagName = m_coreObject->tagName(); >+ if (!tagName.isEmpty()) >+ map.add("tag", tagName); >+ >+ if (auto* element = m_coreObject->element()) { >+ String id = element->getIdAttribute().string(); >+ if (!id.isEmpty()) >+ map.add("id", id); >+ } >+ >+ int level = m_coreObject->isHeading() ? m_coreObject->headingLevel() : m_coreObject->hierarchicalLevel(); >+ if (level) >+ map.add("level", String::number(level)); >+ >+ int rowCount = m_coreObject->axRowCount(); >+ if (rowCount) >+ map.add("rowcount", String::number(rowCount)); >+ >+ int columnCount = m_coreObject->axColumnCount(); >+ if (columnCount) >+ map.add("colcount", String::number(columnCount)); >+ >+ int rowIndex = m_coreObject->axRowIndex(); >+ if (rowIndex != -1) >+ map.add("rowindex", String::number(rowIndex)); >+ >+ int columnIndex = m_coreObject->axColumnIndex(); >+ if (columnIndex != -1) >+ map.add("colindex", String::number(columnIndex)); >+ >+ if (is<AccessibilityTableCell>(m_coreObject)) { >+ auto& cell = downcast<AccessibilityTableCell>(*m_coreObject); >+ int rowSpan = cell.axRowSpan(); >+ if (rowSpan != -1) >+ map.add("rowspan", String::number(rowSpan)); >+ >+ int columnSpan = cell.axColumnSpan(); >+ if (columnSpan != -1) >+ map.add("colspan", String::number(columnSpan)); >+ } >+ >+ String placeholder = m_coreObject->placeholderValue(); >+ if (!placeholder.isEmpty()) >+ map.add("placeholder-text", placeholder); >+ >+ if (m_coreObject->supportsAutoComplete()) >+ map.add("autocomplete", m_coreObject->autoCompleteValue()); >+ >+ if (m_coreObject->supportsHasPopup()) >+ map.add("haspopup", m_coreObject->popupValue()); >+ >+ if (m_coreObject->supportsCurrent()) >+ map.add("current", m_coreObject->currentValue()); >+ >+ if (m_coreObject->supportsPosInSet()) >+ map.add("posinset", String::number(m_coreObject->posInSet())); >+ >+ if (m_coreObject->supportsSetSize()) >+ map.add("setsize", String::number(m_coreObject->setSize())); >+ >+ // The Core AAM states that an explicitly-set value should be exposed, including "none". >+ if (static_cast<AccessibilityObject*>(m_coreObject)->hasAttribute(HTMLNames::aria_sortAttr)) { >+ switch (m_coreObject->sortDirection()) { >+ case AccessibilitySortDirection::Invalid: >+ break; >+ case AccessibilitySortDirection::Ascending: >+ map.add("sort", "ascending"); >+ break; >+ case AccessibilitySortDirection::Descending: >+ map.add("sort", "descending"); >+ break; >+ case AccessibilitySortDirection::Other: >+ map.add("sort", "other"); >+ break; >+ case AccessibilitySortDirection::None: >+ map.add("sort", "none"); >+ break; >+ } >+ } >+ >+ String isReadOnly = m_coreObject->readOnlyValue(); >+ if (!isReadOnly.isEmpty()) >+ map.add("readonly", isReadOnly); >+ >+ String valueDescription = m_coreObject->valueDescription(); >+ if (!valueDescription.isEmpty()) >+ map.add("valuetext", valueDescription); >+ >+ // According to the W3C Core Accessibility API Mappings 1.1, section 5.4.1 General Rules: >+ // "User agents must expose the WAI-ARIA role string if the API supports a mechanism to do so." >+ // In the case of ATK, the mechanism to do so is an object attribute pair (xml-roles:"string"). >+ // We cannot use the computedRoleString for this purpose because it is not limited to elements >+ // with ARIA roles, and it might not contain the actual ARIA role value (e.g. DPub ARIA). >+ String roleString = static_cast<AccessibilityObject*>(m_coreObject)->getAttribute(HTMLNames::roleAttr); >+ if (!roleString.isEmpty()) >+ map.add("xml-roles", roleString); >+ >+ String computedRoleString = m_coreObject->computedRoleString(); >+ if (!computedRoleString.isEmpty()) { >+ map.add("computed-role", computedRoleString); >+ >+ // The HTML AAM maps several elements to ARIA landmark roles. In order for the type of landmark >+ // to be obtainable in the same fashion as an ARIA landmark, fall back on the computedRoleString. >+ // We also want to do this for the style-format-group element types so that the type of format >+ // group it is doesn't get lost to a generic platform role. >+ if (m_coreObject->ariaRoleAttribute() == AccessibilityRole::Unknown && (m_coreObject->isLandmark() || m_coreObject->isStyleFormatGroup())) >+ map.set("xml-roles", computedRoleString); >+ } >+ >+ String roleDescription = m_coreObject->roleDescription(); >+ if (!roleDescription.isEmpty()) >+ map.add("roledescription", roleDescription); >+ >+ String dropEffect = static_cast<AccessibilityObject*>(m_coreObject)->getAttribute(HTMLNames::aria_dropeffectAttr); >+ if (!dropEffect.isEmpty()) >+ map.add("dropeffect", dropEffect); >+ >+ if (m_coreObject->supportsDragging()) >+ map.add("grabbed", m_coreObject->isGrabbed() ? "true" : "false"); >+ >+ String keyShortcuts = m_coreObject->keyShortcutsValue(); >+ if (!keyShortcuts.isEmpty()) >+ map.add("keyshortcuts", keyShortcuts); >+ >+ return map; >+} >+ >+void AccessibilityObjectAtspi::buildAttributes(GVariantBuilder* builder) const >+{ >+ auto attributes = Accessibility::retrieveValueFromMainThread<HashMap<String, String>>([this]() -> HashMap<String, String> { >+ return this->attributes(); >+ }); >+ >+ for (const auto& it : attributes) >+ g_variant_builder_add(builder, "{ss}", it.key.utf8().data(), it.value.utf8().data()); >+} >+ >+HashMap<uint32_t, Vector<RefPtr<AccessibilityObjectAtspi>>> AccessibilityObjectAtspi::relationMap() const >+{ >+ RELEASE_ASSERT(isMainThread()); >+ >+ HashMap<uint32_t, Vector<RefPtr<AccessibilityObjectAtspi>>> map; >+ if (m_coreObject) >+ m_coreObject->updateBackingStore(); >+ >+ if (!m_coreObject) >+ return map; >+ >+ auto addRelation = [&](Atspi::Relation relation, const AccessibilityObject::AccessibilityChildrenVector& children) { >+ Vector<RefPtr<AccessibilityObjectAtspi>> wrappers; >+ for (const auto& child : children) { >+ if (auto* wrapper = child->wrapper()) >+ wrappers.append(wrapper); >+ } >+ if (!wrappers.isEmpty()) >+ map.add(relation, WTFMove(wrappers)); >+ >+ }; >+ >+ AccessibilityObject::AccessibilityChildrenVector ariaLabelledByElements; >+ if (m_coreObject->isControl()) { >+ if (auto* label = m_coreObject->correspondingLabelForControlElement()) >+ ariaLabelledByElements.append(label); >+ } else if (m_coreObject->isFieldset()) { >+ if (auto* label = m_coreObject->titleUIElement()) >+ ariaLabelledByElements.append(label); >+ } else if (m_coreObject->roleValue() == AccessibilityRole::Legend) { >+ if (auto* renderFieldset = ancestorsOfType<RenderBlock>(*m_coreObject->renderer()).first()) { >+ if (renderFieldset->isFieldset()) >+ ariaLabelledByElements.append(m_coreObject->axObjectCache()->getOrCreate(renderFieldset)); >+ } >+ } else if (!m_coreObject->correspondingControlForLabelElement()) >+ m_coreObject->ariaLabelledByElements(ariaLabelledByElements); >+ addRelation(Atspi::LabelledBy, ariaLabelledByElements); >+ >+ AccessibilityObject::AccessibilityChildrenVector ariaLabelledByReferencingElements; >+ if (auto* control = m_coreObject->correspondingControlForLabelElement()) >+ ariaLabelledByReferencingElements.append(control); >+ else >+ m_coreObject->ariaLabelledByReferencingElements(ariaLabelledByReferencingElements); >+ addRelation(Atspi::LabelFor, ariaLabelledByReferencingElements); >+ >+ AccessibilityObject::AccessibilityChildrenVector ariaFlowToElements; >+ m_coreObject->ariaFlowToElements(ariaFlowToElements); >+ addRelation(Atspi::FlowsTo, ariaFlowToElements); >+ >+ AccessibilityObject::AccessibilityChildrenVector ariaFlowToReferencingElements; >+ m_coreObject->ariaFlowToReferencingElements(ariaFlowToReferencingElements); >+ addRelation(Atspi::FlowsFrom, ariaFlowToReferencingElements); >+ >+ AccessibilityObject::AccessibilityChildrenVector ariaDescribedByElements; >+ m_coreObject->ariaDescribedByElements(ariaDescribedByElements); >+ addRelation(Atspi::DescribedBy, ariaDescribedByElements); >+ >+ AccessibilityObject::AccessibilityChildrenVector ariaDescribedByReferencingElements; >+ m_coreObject->ariaDescribedByReferencingElements(ariaDescribedByReferencingElements); >+ addRelation(Atspi::DescriptionFor, ariaDescribedByReferencingElements); >+ >+ AccessibilityObject::AccessibilityChildrenVector ariaControlsElements; >+ m_coreObject->ariaControlsElements(ariaControlsElements); >+ addRelation(Atspi::ControllerFor, ariaControlsElements); >+ >+ AccessibilityObject::AccessibilityChildrenVector ariaControlsReferencingElements; >+ m_coreObject->ariaControlsReferencingElements(ariaControlsReferencingElements); >+ addRelation(Atspi::ControlledBy, ariaControlsReferencingElements); >+ >+ AccessibilityObject::AccessibilityChildrenVector ariaOwnsElements; >+ m_coreObject->ariaOwnsElements(ariaOwnsElements); >+ addRelation(Atspi::NodeParentOf, ariaOwnsElements); >+ >+ AccessibilityObject::AccessibilityChildrenVector ariaOwnsReferencingElements; >+ m_coreObject->ariaOwnsReferencingElements(ariaOwnsReferencingElements); >+ addRelation(Atspi::NodeChildOf, ariaOwnsReferencingElements); >+ >+ AccessibilityObject::AccessibilityChildrenVector ariaDetailsElements; >+ m_coreObject->ariaDetailsElements(ariaDetailsElements); >+ addRelation(Atspi::Details, ariaDetailsElements); >+ >+ AccessibilityObject::AccessibilityChildrenVector ariaDetailsReferencingElements; >+ m_coreObject->ariaDetailsReferencingElements(ariaDetailsReferencingElements); >+ addRelation(Atspi::DetailsFor, ariaDetailsReferencingElements); >+ >+ AccessibilityObject::AccessibilityChildrenVector ariaErrorMessageElements; >+ m_coreObject->ariaErrorMessageElements(ariaErrorMessageElements); >+ addRelation(Atspi::ErrorMessage, ariaErrorMessageElements); >+ >+ AccessibilityObject::AccessibilityChildrenVector ariaErrorMessageReferencingElements; >+ m_coreObject->ariaErrorMessageReferencingElements(ariaErrorMessageReferencingElements); >+ addRelation(Atspi::ErrorFor, ariaErrorMessageReferencingElements); >+ >+ return map; >+} >+ >+void AccessibilityObjectAtspi::buildRelationSet(GVariantBuilder* builder) const >+{ >+ auto relationMap = Accessibility::retrieveValueFromMainThread<HashMap<uint32_t, Vector<RefPtr<AccessibilityObjectAtspi>>>>([this]() -> HashMap<uint32_t, Vector<RefPtr<AccessibilityObjectAtspi>>> { >+ return this->relationMap(); >+ }); >+ >+ for (const auto& it : relationMap) { >+ GVariantBuilder arrayBuilder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("a(so)")); >+ for (const auto& atspiObject : it.value) { >+ atspiObject->setRoot(root()); >+ g_variant_builder_add(&arrayBuilder, "@(so)", atspiObject->reference()); >+ } >+ g_variant_builder_add(builder, "(ua(so))", it.key, &arrayBuilder); >+ } >+} >+ >+void AccessibilityObjectAtspi::buildInterfaces(GVariantBuilder* builder) const >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ if (m_interfaces.contains(Interface::Accessible)) >+ g_variant_builder_add(builder, "s", webkit_accessible_interface.name); >+} >+ >+void AccessibilityObjectAtspi::serialize(GVariantBuilder* builder) const >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ auto* atspiRoot = root(); >+ g_variant_builder_add(builder, "(so)", atspiRoot->atspi().uniqueName(), m_path.utf8().data()); >+ g_variant_builder_add(builder, "(so)", atspiRoot->parentUniqueName().utf8().data(), "/org/a11y/atspi/accessible/root"); >+ g_variant_builder_add(builder, "@(so)", parentReference()); >+ >+ g_variant_builder_add(builder, "i", indexInParent()); >+ g_variant_builder_add(builder, "i", childCount()); >+ >+ GVariantBuilder interfaces = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("as")); >+ buildInterfaces(&interfaces); >+ g_variant_builder_add(builder, "@as", g_variant_new("as", &interfaces)); >+ >+ g_variant_builder_add(builder, "s", name().data()); >+ >+ g_variant_builder_add(builder, "u", role()); >+ >+ g_variant_builder_add(builder, "s", description().data()); >+ >+ GVariantBuilder states = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("au")); >+ auto atspiStates = state(); >+ g_variant_builder_add(&states, "u", static_cast<uint32_t>(atspiStates & 0xffffffff)); >+ g_variant_builder_add(&states, "u", static_cast<uint32_t>(atspiStates >> 32)); >+ g_variant_builder_add(builder, "@au", g_variant_builder_end(&states)); >+} >+ >+void AccessibilityObjectAtspi::childAdded(AccessibilityObjectAtspi& child) >+{ >+ RELEASE_ASSERT(isMainThread()); >+ if (!m_isRegistered.load()) >+ return; >+ >+ RunLoop::main().dispatch([this, protectedThis = Ref { *this }, child = Ref { child }] { >+ if (!m_coreObject) >+ return; >+ root()->atspi().childrenChanged(*this, child, AccessibilityAtspi::ChildrenChanged::Added); >+ }); >+} >+ >+void AccessibilityObjectAtspi::childRemoved(AccessibilityObjectAtspi& child) >+{ >+ RELEASE_ASSERT(isMainThread()); >+ if (!m_isRegistered.load()) >+ return; >+ >+ root()->atspi().childrenChanged(*this, child, AccessibilityAtspi::ChildrenChanged::Removed); >+} >+ >+void AccessibilityObjectAtspi::stateChanged(const char* name, bool value) >+{ >+ RELEASE_ASSERT(isMainThread()); >+ if (!m_isRegistered.load()) >+ return; >+ >+ root()->atspi().stateChanged(*this, name, value); >+} >+ >+unsigned AccessibilityObjectAtspi::role() const >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ if (!m_axObject) >+ return Atspi::Role::Unknown; >+ >+ return atspiRole(m_axObject->roleValue()); >+} >+ >+String AccessibilityObjectAtspi::roleName() const >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ if (!m_axObject) >+ return "invalid"; >+ >+ return m_axObject->rolePlatformString(); >+} >+ >+const char* AccessibilityObjectAtspi::localizedRoleName() const >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ if (!m_axObject) >+ return _("invalid"); >+ >+ return AccessibilityAtspi::localizedRoleName(m_axObject->roleValue()); >+} >+ >+void AccessibilityObjectAtspi::updateBackingStore() >+{ >+ if (isMainThread()) { >+ if (m_coreObject) >+ m_coreObject->updateBackingStore(); >+ return; >+ } >+ >+ if (m_axObject) >+ m_axObject->updateBackingStore(); > } > > void AccessibilityObject::detachPlatformWrapper(AccessibilityDetachmentType detachmentType) > { >+ switch (detachmentType) { >+ case AccessibilityDetachmentType::CacheDestroyed: >+ wrapper()->cacheDestroyed(); >+ break; >+ case AccessibilityDetachmentType::ElementDestroyed: >+ wrapper()->elementDestroyed(); >+ break; >+ case AccessibilityDetachmentType::ElementChanged: >+ RELEASE_ASSERT_NOT_REACHED(); >+ break; >+ } > } > > bool AccessibilityObject::accessibilityIgnoreAttachment() const >@@ -45,6 +1157,29 @@ bool AccessibilityObject::accessibilityIgnoreAttachment() const > > AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesObject() const > { >+ RELEASE_ASSERT(isMainThread()); >+ >+ auto* parent = parentObject(); >+ if (!parent) >+ return AccessibilityObjectInclusion::DefaultBehavior; >+ >+ // If the author has provided a role, platform-specific inclusion likely doesn't apply. >+ if (ariaRoleAttribute() != AccessibilityRole::Unknown) >+ return AccessibilityObjectInclusion::DefaultBehavior; >+ >+ // Never expose an unknown object, since AT's won't know what to >+ // do with them. This is what is done on the Mac as well. >+ if (roleValue() == AccessibilityRole::Unknown) >+ return AccessibilityObjectInclusion::IgnoreObject; >+ >+ // The object containing the text should implement org.a11y.atspi.Text itself. >+ if (roleValue() == AccessibilityRole::StaticText) >+ return AccessibilityObjectInclusion::IgnoreObject; >+ >+ // Entries and password fields have extraneous children which we want to ignore. >+ if (parent->isPasswordField() || parent->isTextControl()) >+ return AccessibilityObjectInclusion::IgnoreObject; >+ > return AccessibilityObjectInclusion::DefaultBehavior; > } > >diff --git a/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.h b/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.h >index 4305c0b6ff79..56630eeac5b3 100644 >--- a/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.h >+++ b/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.h >@@ -20,19 +20,88 @@ > #pragma once > > #if ENABLE(ACCESSIBILITY) && USE(ATSPI) >+#include "AccessibilityAtspi.h" >+#include "AccessibilityObjectInterface.h" > #include <wtf/Atomics.h> >+#include <wtf/Lock.h> >+#include <wtf/OptionSet.h> > #include <wtf/ThreadSafeRefCounted.h> >+#include <wtf/text/CString.h> >+ >+typedef struct _GDBusInterfaceVTable GDBusInterfaceVTable; >+typedef struct _GVariant GVariant; >+typedef struct _GVariantBuilder GVariantBuilder; > > namespace WebCore { > class AXCoreObject; >+class AccessibilityRootAtspi; > > class AccessibilityObjectAtspi final : public ThreadSafeRefCounted<AccessibilityObjectAtspi> { > public: > static Ref<AccessibilityObjectAtspi> create(AXCoreObject*); > ~AccessibilityObjectAtspi() = default; > >+ enum class Interface : uint8_t { >+ Accessible = 1 << 0 >+ }; >+ const OptionSet<Interface>& interfaces() const { return m_interfaces; } >+ >+ void setRoot(AccessibilityRootAtspi*); >+ AccessibilityRootAtspi* root() const; >+ void setParent(std::optional<AccessibilityObjectAtspi*>); >+ std::optional<AccessibilityObjectAtspi*> parent() const; >+ void updateBackingStore(); >+ >+ void attach(AXCoreObject*); >+ void detach(); >+ void elementDestroyed(); >+ void cacheDestroyed(); >+ >+ int indexInParentForChildrenChanged(AccessibilityAtspi::ChildrenChanged); >+ >+ const String& path(); >+ GVariant* reference(); >+ void serialize(GVariantBuilder*) const; >+ >+ String id() const; >+ CString name() const; >+ CString description() const; >+ unsigned role() const; >+ unsigned childCount() const; >+ Vector<RefPtr<AccessibilityObjectAtspi>> children() const; >+ AccessibilityObjectAtspi* childAt(unsigned) const; >+ uint64_t state() const; >+ void stateChanged(const char*, bool); >+ HashMap<String, String> attributes() const; >+ HashMap<uint32_t, Vector<RefPtr<AccessibilityObjectAtspi>>> relationMap() const; >+ > private: > explicit AccessibilityObjectAtspi(AXCoreObject*); >+ >+ int indexInParent() const; >+ GVariant* parentReference() const; >+ void childAdded(AccessibilityObjectAtspi&); >+ void childRemoved(AccessibilityObjectAtspi&); >+ >+ String roleName() const; >+ const char* localizedRoleName() const; >+ void buildAttributes(GVariantBuilder*) const; >+ void buildRelationSet(GVariantBuilder*) const; >+ void buildInterfaces(GVariantBuilder*) const; >+ >+ static OptionSet<Interface> interfacesForObject(AXCoreObject&); >+ >+ static GDBusInterfaceVTable s_accessibleFunctions; >+ >+ AXCoreObject* m_axObject { nullptr }; >+ AXCoreObject* m_coreObject { nullptr }; >+ OptionSet<Interface> m_interfaces; >+ AccessibilityRootAtspi* m_root WTF_GUARDED_BY_LOCK(m_rootLock) { nullptr }; >+ std::optional<AccessibilityObjectAtspi*> m_parent; >+ Atomic<bool> m_isRegistered { false }; >+ String m_path; >+ mutable int m_indexInParent { -1 }; >+ mutable Lock m_rootLock; > }; > > } // namespace WebCore >diff --git a/Source/WebCore/accessibility/atspi/AccessibilityRootAtspi.cpp b/Source/WebCore/accessibility/atspi/AccessibilityRootAtspi.cpp >index a0b948861e2d..82b57892c4b2 100644 >--- a/Source/WebCore/accessibility/atspi/AccessibilityRootAtspi.cpp >+++ b/Source/WebCore/accessibility/atspi/AccessibilityRootAtspi.cpp >@@ -21,7 +21,11 @@ > #include "AccessibilityRootAtspi.h" > > #if ENABLE(ACCESSIBILITY) && USE(ATSPI) >+#include "AXObjectCache.h" >+#include "AccessibilityAtspiEnums.h" > #include "AccessibilityAtspiInterfaces.h" >+#include "Document.h" >+#include "Frame.h" > #include "Page.h" > #include <glib/gi18n-lib.h> > #include <wtf/MainThread.h> >@@ -44,10 +48,9 @@ GDBusInterfaceVTable AccessibilityRootAtspi::s_accessibleFunctions = { > // method_call > [](GDBusConnection*, const gchar* sender, const gchar*, const gchar*, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) { > RELEASE_ASSERT(!isMainThread()); >- > auto& rootObject = *static_cast<AccessibilityRootAtspi*>(userData); > if (!g_strcmp0(methodName, "GetRole")) >- g_dbus_method_invocation_return_value(invocation, g_variant_new("(u)", 0)); >+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(u)", Atspi::Role::Filler)); > else if (!g_strcmp0(methodName, "GetRoleName")) > g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", "filler")); > else if (!g_strcmp0(methodName, "GetLocalizedRoleName")) >@@ -64,9 +67,10 @@ GDBusInterfaceVTable AccessibilityRootAtspi::s_accessibleFunctions = { > #endif > GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("(au)")); > >+ uint64_t atspiStates = (G_GUINT64_CONSTANT(1) << Atspi::State::ManagesDescendants); > g_variant_builder_open(&builder, G_VARIANT_TYPE("au")); >- g_variant_builder_add(&builder, "u", 0); >- g_variant_builder_add(&builder, "u", 0); >+ g_variant_builder_add(&builder, "u", static_cast<uint32_t>(atspiStates & 0xffffffff)); >+ g_variant_builder_add(&builder, "u", static_cast<uint32_t>(atspiStates >> 32)); > g_variant_builder_close(&builder); > > g_dbus_method_invocation_return_value(invocation, g_variant_builder_end(&builder)); >@@ -84,10 +88,26 @@ GDBusInterfaceVTable AccessibilityRootAtspi::s_accessibleFunctions = { > g_dbus_method_invocation_return_value(invocation, g_variant_builder_end(&builder)); > } else if (!g_strcmp0(methodName, "GetApplication")) > g_dbus_method_invocation_return_value(invocation, g_variant_new("(@(so))", rootObject.applicationReference())); >- else if (!g_strcmp0(methodName, "GetChildAtIndex")) >+ else if (!g_strcmp0(methodName, "GetChildAtIndex")) { >+ int index; >+ g_variant_get(parameters, "(i)", &index); >+ if (!index) { >+ auto* child = Accessibility::retrieveValueFromMainThread<AccessibilityObjectAtspi*>([&rootObject]() -> AccessibilityObjectAtspi* { >+ return rootObject.child(); >+ }); >+ if (child) { >+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(@(so))", child->reference())); >+ return; >+ } >+ } > g_dbus_method_invocation_return_value(invocation, g_variant_new("(@(so))", rootObject.atspi().nullReference())); >- else if (!g_strcmp0(methodName, "GetChildren")) { >+ } else if (!g_strcmp0(methodName, "GetChildren")) { > GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("a(so)")); >+ auto* child = Accessibility::retrieveValueFromMainThread<AccessibilityObjectAtspi*>([&rootObject]() -> AccessibilityObjectAtspi* { >+ return rootObject.child(); >+ }); >+ if (child) >+ g_variant_builder_add(&builder, "@(so)", child->reference()); > g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(so))", &builder)); > } else if (!g_strcmp0(methodName, "GetIndexInParent")) { > g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", 0)); >@@ -102,9 +122,8 @@ GDBusInterfaceVTable AccessibilityRootAtspi::s_accessibleFunctions = { > } > }, > // get_property >- [](GDBusConnection*, const gchar* sender, const gchar*, const gchar*, const gchar* propertyName, GError** error, gpointer userData) -> GVariant* { >+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* propertyName, GError** error, gpointer userData) -> GVariant* { > RELEASE_ASSERT(!isMainThread()); >- > auto& rootObject = *static_cast<AccessibilityRootAtspi*>(userData); > if (!g_strcmp0(propertyName, "Name")) > return g_variant_new_string(""); >@@ -116,8 +135,12 @@ GDBusInterfaceVTable AccessibilityRootAtspi::s_accessibleFunctions = { > return g_variant_new_string(""); > if (!g_strcmp0(propertyName, "Parent")) > return g_variant_new("(so)", rootObject.m_parentUniqueName.utf8().data(), rootObject.m_parentPath.utf8().data()); >- if (!g_strcmp0(propertyName, "ChildCount")) >- return g_variant_new_int32(0); >+ if (!g_strcmp0(propertyName, "ChildCount")) { >+ auto childCount = Accessibility::retrieveValueFromMainThread<int32_t>([&rootObject]() -> int32_t { >+ return rootObject.child() ? 1 : 0; >+ }); >+ return g_variant_new_int32(childCount); >+ } > > g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Unknown property '%s'", propertyName); > return nullptr; >@@ -136,6 +159,12 @@ void AccessibilityRootAtspi::registerObject(CompletionHandler<void(const String& > m_atspi.registerRoot(*this, WTFMove(interfaces), WTFMove(completionHandler)); > } > >+void AccessibilityRootAtspi::unregisterObject() >+{ >+ RELEASE_ASSERT(isMainThread()); >+ m_atspi.unregisterRoot(*this); >+} >+ > void AccessibilityRootAtspi::setPath(String&& path) > { > RELEASE_ASSERT(!isMainThread()); >@@ -162,6 +191,66 @@ GVariant* AccessibilityRootAtspi::reference() const > return g_variant_new("(so)", m_atspi.uniqueName(), m_path.utf8().data()); > } > >+AccessibilityObjectAtspi* AccessibilityRootAtspi::child() const >+{ >+ RELEASE_ASSERT(isMainThread()); >+ if (!AXObjectCache::accessibilityEnabled()) >+ AXObjectCache::enableAccessibility(); >+ >+ if (!m_page) >+ return nullptr; >+ >+ Frame& frame = m_page->mainFrame(); >+ if (!frame.document()) >+ return nullptr; >+ >+ AXObjectCache* cache = frame.document()->axObjectCache(); >+ if (!cache) >+ return nullptr; >+ >+ AXCoreObject* rootObject = cache->rootObject(); >+ if (!rootObject) >+ return nullptr; >+ >+ auto* wrapper = rootObject->wrapper(); >+ if (!wrapper) >+ return nullptr; >+ >+ wrapper->setRoot(const_cast<AccessibilityRootAtspi*>(this)); >+ wrapper->setParent(nullptr); // nullptr parent means root. >+ >+ return wrapper; >+} >+ >+void AccessibilityRootAtspi::serialize(GVariantBuilder* builder) const >+{ >+ RELEASE_ASSERT(!isMainThread()); >+ g_variant_builder_add(builder, "(so)", m_atspi.uniqueName(), m_path.utf8().data()); >+ g_variant_builder_add(builder, "(so)", m_parentUniqueName.utf8().data(), "/org/a11y/atspi/accessible/root"); >+ g_variant_builder_add(builder, "(so)", m_parentUniqueName.utf8().data(), m_parentPath.utf8().data()); >+ >+ g_variant_builder_add(builder, "i", 0); >+ g_variant_builder_add(builder, "i", Accessibility::retrieveValueFromMainThread<int32_t>([this]() -> int32_t { >+ return child() ? 1 : 0; >+ })); >+ >+ GVariantBuilder interfaces = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("as")); >+ g_variant_builder_add(&interfaces, "s", webkit_accessible_interface.name); >+ g_variant_builder_add(builder, "@as", g_variant_new("as", &interfaces)); >+ >+ g_variant_builder_add(builder, "s", ""); >+ >+ g_variant_builder_add(builder, "u", Atspi::Role::Filler); >+ >+ g_variant_builder_add(builder, "s", ""); >+ >+ GVariantBuilder states = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("au")); >+ uint64_t atspiStates = (G_GUINT64_CONSTANT(1) << Atspi::State::ManagesDescendants); >+ g_variant_builder_add(&states, "u", static_cast<uint32_t>(atspiStates & 0xffffffff)); >+ g_variant_builder_add(&states, "u", static_cast<uint32_t>(atspiStates >> 32)); >+ g_variant_builder_add(builder, "@au", g_variant_builder_end(&states)); >+} >+ > } // namespace WebCore > > #endif // ENABLE(ACCESSIBILITY) && USE(ATSPI) >diff --git a/Source/WebCore/accessibility/atspi/AccessibilityRootAtspi.h b/Source/WebCore/accessibility/atspi/AccessibilityRootAtspi.h >index 406da10610dc..ec19be1bf0a5 100644 >--- a/Source/WebCore/accessibility/atspi/AccessibilityRootAtspi.h >+++ b/Source/WebCore/accessibility/atspi/AccessibilityRootAtspi.h >@@ -38,12 +38,18 @@ public: > ~AccessibilityRootAtspi() = default; > > void registerObject(CompletionHandler<void(const String&)>&&); >+ void unregisterObject(); > void setPath(String&&); > void setParentPath(String&&); > >+ const String& path() const { return m_path; } >+ const String& parentUniqueName() const { return m_parentUniqueName; } > GVariant* reference() const; > GVariant* applicationReference() const; > AccessibilityAtspi& atspi() const { return m_atspi; } >+ AccessibilityObjectAtspi* child() const; >+ >+ void serialize(GVariantBuilder*) const; > > private: > AccessibilityRootAtspi(const Page&, AccessibilityAtspi&); >diff --git a/Source/WebCore/accessibility/isolatedtree/atspi/AXIsolatedObjectAtspi.cpp b/Source/WebCore/accessibility/isolatedtree/atspi/AXIsolatedObjectAtspi.cpp >index 9d3dd0c24793..71ce117ff6b9 100644 >--- a/Source/WebCore/accessibility/isolatedtree/atspi/AXIsolatedObjectAtspi.cpp >+++ b/Source/WebCore/accessibility/isolatedtree/atspi/AXIsolatedObjectAtspi.cpp >@@ -31,11 +31,13 @@ void AXIsolatedObject::initializePlatformProperties(const AXCoreObject&, bool) > > void AXIsolatedObject::attachPlatformWrapper(AccessibilityObjectWrapper* wrapper) > { >+ wrapper->attach(this); > setWrapper(wrapper); > } > >-void AXIsolatedObject::detachPlatformWrapper(AccessibilityDetachmentType detachmentType) >+void AXIsolatedObject::detachPlatformWrapper(AccessibilityDetachmentType) > { >+ wrapper()->detach(); > } > > } // namespace WebCore >diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog >index 85bacbd859ce..466fcd93c192 100644 >--- a/Source/WebKit/ChangeLog >+++ b/Source/WebKit/ChangeLog >@@ -1,3 +1,13 @@ >+2021-09-30 Carlos Garcia Campos <cgarcia@igalia.com> >+ >+ [GTK][a11y] Add initial implementation of accessible interface when building with ATSPI >+ https://bugs.webkit.org/show_bug.cgi?id=230256 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * WebProcess/WebPage/gtk/WebPageGtk.cpp: >+ (WebKit::WebPage::platformDetach): Unregister the page accessible object. >+ > 2021-09-30 Carlos Garcia Campos <cgarcia@igalia.com> > > [GTK][a11y] Connect UI process a11y tree with the web process when building with ATSPI >diff --git a/Source/WebKit/WebProcess/WebPage/gtk/WebPageGtk.cpp b/Source/WebKit/WebProcess/WebPage/gtk/WebPageGtk.cpp >index a89ddfbfce5b..e35066f35385 100644 >--- a/Source/WebKit/WebProcess/WebPage/gtk/WebPageGtk.cpp >+++ b/Source/WebKit/WebProcess/WebPage/gtk/WebPageGtk.cpp >@@ -91,6 +91,10 @@ void WebPage::platformReinitialize() > > void WebPage::platformDetach() > { >+#if USE(ATSPI) >+ if (m_accessibilityRootObject) >+ m_accessibilityRootObject->unregisterObject(); >+#endif > } > > bool WebPage::performDefaultBehaviorForKeyEvent(const WebKeyboardEvent& keyboardEvent) >diff --git a/Tools/ChangeLog b/Tools/ChangeLog >index 40bd298e9891..0f07dabb6e0f 100644 >--- a/Tools/ChangeLog >+++ b/Tools/ChangeLog >@@ -1,3 +1,26 @@ >+2021-09-30 Carlos Garcia Campos <cgarcia@igalia.com> >+ >+ [GTK][a11y] Add initial implementation of accessible interface when building with ATSPI >+ https://bugs.webkit.org/show_bug.cgi?id=230256 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Add more unit tests for the accessible interface. >+ >+ * TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp: >+ (AtspiEventDeleter::operator() const): >+ (AccessibilityTest::startEventMonitor): >+ (AccessibilityTest::stopEventMonitor): >+ (AccessibilityTest::stateSetSize): >+ (testAccessibleBasicHierarchy): >+ (testAccessibleIgnoredObjects): >+ (testAccessibleChildrenChanged): >+ (testAccessibleAttributes): >+ (testAccessibleState): >+ (testAccessibleStateChanged): >+ (beforeAll): >+ (testAtspiBasicHierarchy): Deleted. >+ > 2021-09-29 Alex Christensen <achristensen@webkit.org> > > Terminate PCM daemon before and after unit test that uses it >diff --git a/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp b/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp >index 1a456b80d381..50151b36b367 100644 >--- a/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp >+++ b/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp >@@ -20,12 +20,22 @@ > #include "config.h" > > #include "WebViewTest.h" >+#include <wtf/MonotonicTime.h> > > // The libatspi headers don't use G_BEGIN_DECLS > extern "C" { > #include <atspi/atspi.h> > } > >+struct AtspiEventDeleter { >+ void operator()(AtspiEvent* event) const >+ { >+ g_boxed_free(ATSPI_TYPE_EVENT, event); >+ } >+}; >+ >+using UniqueAtspiEvent = std::unique_ptr<AtspiEvent, AtspiEventDeleter>; >+ > class AccessibilityTest : public WebViewTest { > public: > MAKE_GLIB_TEST_FIXTURE(AccessibilityTest); >@@ -88,11 +98,50 @@ public: > m_eventSource = nullptr; > } > >+ void startEventMonitor(AtspiAccessible* source, const Vector<const char*>& events) >+ { >+ m_eventMonitor.source = source; >+ m_eventMonitor.listener = adoptGRef(atspi_event_listener_new([](AtspiEvent* event, gpointer userData) { >+ auto* test = static_cast<AccessibilityTest*>(userData); >+ if (event->source == test->m_eventMonitor.source) >+ test->m_eventMonitor.events.append(static_cast<AtspiEvent*>(g_boxed_copy(ATSPI_TYPE_EVENT, event))); >+ }, this, nullptr)); >+ >+ for (const auto* event : events) >+ atspi_event_listener_register(m_eventMonitor.listener.get(), event, nullptr); >+ } >+ >+ Vector<UniqueAtspiEvent> stopEventMonitor(unsigned expectedEvents, std::optional<Seconds> timeout = std::nullopt) >+ { >+ // If events is empty wait for the events. >+ auto startTime = MonotonicTime::now(); >+ while (m_eventMonitor.events.size() < expectedEvents && MonotonicTime::now() - startTime < timeout.value_or(Seconds::infinity())) >+ g_main_context_iteration(nullptr, timeout ? FALSE : TRUE); >+ >+ auto events = WTFMove(m_eventMonitor.events); >+ m_eventMonitor = { nullptr, { }, nullptr }; >+ return events; >+ } >+ >+ static unsigned stateSetSize(AtspiStateSet* stateSet) >+ { >+ GArray* states = atspi_state_set_get_states(stateSet); >+ unsigned length = states->len; >+ g_array_free(states, TRUE); >+ return length; >+ } >+ > private: > AtspiAccessible* m_eventSource { nullptr }; >+ >+ struct { >+ GRefPtr<AtspiEventListener> listener; >+ Vector<UniqueAtspiEvent> events; >+ AtspiAccessible* source { nullptr }; >+ } m_eventMonitor; > }; > >-static void testAtspiBasicHierarchy(AccessibilityTest* test, gconstpointer) >+static void testAccessibleBasicHierarchy(AccessibilityTest* test, gconstpointer) > { > test->showInWindow(); > test->loadHtml( >@@ -152,9 +201,15 @@ static void testAtspiBasicHierarchy(AccessibilityTest* test, gconstpointer) > " </body>" > "</html>", > nullptr); >+#if USE(ATSPI) >+ // In atspi implementation the root object doesn't emit children-changed because it >+ // always has ManagesDescendants in state. >+ test->waitUntilLoadFinished(); >+#else > // Check that children-changed::remove is emitted on the root object on navigation, > // and the a11y hierarchy is updated. > test->waitUntilChildrenRemoved(rootObject.get()); >+#endif > > documentWeb = test->findDocumentWeb(testApp.get()); > g_assert_true(ATSPI_IS_ACCESSIBLE(documentWeb.get())); >@@ -175,9 +230,496 @@ static void testAtspiBasicHierarchy(AccessibilityTest* test, gconstpointer) > g_assert_cmpint(atspi_accessible_get_role(img.get(), nullptr), ==, ATSPI_ROLE_IMAGE); > } > >+static void testAccessibleIgnoredObjects(AccessibilityTest* test, gconstpointer) >+{ >+ test->showInWindow(); >+ test->loadHtml( >+ "<html>" >+ " <body>" >+ " <div>" >+ " <p>Static text nodes are ignored, so this paragraph shouldn't have a child</p>" >+ " </div>" >+ " </body>" >+ "</html>", >+ nullptr); >+ test->waitUntilLoadFinished(); >+ >+ auto testApp = test->findTestApplication(); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(testApp.get())); >+ >+ auto documentWeb = test->findDocumentWeb(testApp.get()); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(documentWeb.get())); >+ g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 1); >+ >+ auto p = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(p.get())); >+ g_assert_cmpint(atspi_accessible_get_role(p.get(), nullptr), ==, ATSPI_ROLE_PARAGRAPH); >+ g_assert_cmpint(atspi_accessible_get_child_count(p.get(), nullptr), ==, 0); >+} >+ >+static void testAccessibleChildrenChanged(AccessibilityTest* test, gconstpointer) >+{ >+#if !USE(ATSPI) >+ g_test_skip("This test doesn't work with ATK"); >+#else >+ test->showInWindow(); >+ test->loadHtml( >+ "<html>" >+ " <body>" >+ " <div id='grandparent'>" >+ " <div id='parent'>" >+ " <p>Foo</p><p>Bar</p>" >+ " </div>" >+ " </div>" >+ " </body>" >+ "</html>", >+ nullptr); >+ test->waitUntilLoadFinished(); >+ >+ auto testApp = test->findTestApplication(); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(testApp.get())); >+ >+ auto documentWeb = test->findDocumentWeb(testApp.get()); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(documentWeb.get())); >+ // The divs are not exposed to ATs. >+ g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 2); >+ >+ auto foo = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(foo.get())); >+ g_assert_cmpint(atspi_accessible_get_child_count(foo.get(), nullptr), ==, 0); >+ >+ auto bar = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 1, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(bar.get())); >+ g_assert_cmpint(atspi_accessible_get_child_count(bar.get(), nullptr), ==, 0); >+ >+ // Add a new paragraph. >+ test->startEventMonitor(documentWeb.get(), { "object:children-changed:add", "object:children-changed:remove" }); >+ test->runJavaScriptAndWaitUntilFinished("let p = document.createElement('p'); p.innerText = 'Baz'; document.getElementById('parent').appendChild(p);", nullptr); >+ auto events = test->stopEventMonitor(1); >+ g_assert_cmpuint(events.size(), ==, 1); >+ g_assert_cmpstr(events[0]->type, ==, "object:children-changed:add"); >+ g_assert_cmpuint(events[0]->detail1, ==, 2); // New node added to position 2. >+ g_assert_true(G_VALUE_HOLDS_OBJECT(&events[0]->any_data)); >+ auto baz = adoptGRef(static_cast<AtspiAccessible*>(g_value_dup_object(&events[0]->any_data))); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(baz.get())); >+ g_assert_cmpint(atspi_accessible_get_role(baz.get(), nullptr), ==, ATSPI_ROLE_PARAGRAPH); >+ g_assert_true(atspi_accessible_get_parent(baz.get(), nullptr) == documentWeb.get()); >+ g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 3); >+ GRefPtr<AtspiAccessible> child = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr)); >+ g_assert_true(foo.get() == child.get()); >+ child = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 1, nullptr)); >+ g_assert_true(bar.get() == child.get()); >+ child = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 2, nullptr)); >+ g_assert_true(baz.get() == child.get()); >+ child = nullptr; >+ events = { }; >+ >+ // Remove one of the paragraphs. >+ test->startEventMonitor(documentWeb.get(), { "object:children-changed:add", "object:children-changed:remove" }); >+ test->runJavaScriptAndWaitUntilFinished("let div = document.getElementById('parent'); div.removeChild(div.children[0]);", nullptr); >+ events = test->stopEventMonitor(1); >+ g_assert_cmpuint(events.size(), ==, 1); >+ g_assert_cmpstr(events[0]->type, ==, "object:children-changed:remove"); >+ g_assert_cmpuint(events[0]->detail1, ==, 0); // Remove node was at position 0. >+ g_assert_true(G_VALUE_HOLDS_OBJECT(&events[0]->any_data)); >+ g_assert_true(g_value_get_object(&events[0]->any_data) == foo.get()); >+ GRefPtr<AtspiStateSet> set = adoptGRef(atspi_accessible_get_state_set(foo.get())); >+ g_assert_true(atspi_state_set_contains(set.get(), ATSPI_STATE_DEFUNCT)); >+ g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 2); >+ child = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr)); >+ g_assert_true(bar.get() == child.get()); >+ child = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 1, nullptr)); >+ g_assert_true(baz.get() == child.get()); >+ child = nullptr; >+ events = { }; >+ >+ // Changing role causes an internal detach + attach that shouldn't be exposed to ATs. >+ test->startEventMonitor(documentWeb.get(), { "object:children-changed:add", "object:children-changed:remove" }); >+ test->runJavaScriptAndWaitUntilFinished("document.getElementById('parent').children[0].role='button'", nullptr); >+ events = test->stopEventMonitor(0, 0.5_s); >+ g_assert_cmpuint(events.size(), ==, 0); >+ g_assert_cmpint(atspi_accessible_get_role(bar.get(), nullptr), ==, ATSPI_ROLE_PUSH_BUTTON); >+ events = { }; >+ >+ // Remove the container. >+ test->startEventMonitor(documentWeb.get(), { "object:children-changed:add", "object:children-changed:remove" }); >+ test->runJavaScriptAndWaitUntilFinished("document.getElementById('grandparent').removeChild(document.getElementById('parent'));", nullptr); >+ events = test->stopEventMonitor(2); >+ g_assert_cmpuint(events.size(), ==, 2); >+ g_assert_cmpstr(events[0]->type, ==, "object:children-changed:remove"); >+ g_assert_cmpstr(events[1]->type, ==, "object:children-changed:remove"); >+ g_assert_true(G_VALUE_HOLDS_OBJECT(&events[0]->any_data)); >+ g_assert_true(G_VALUE_HOLDS_OBJECT(&events[1]->any_data)); >+ g_assert_true(g_value_get_object(&events[0]->any_data) == bar.get() || g_value_get_object(&events[0]->any_data) == baz.get()); >+ g_assert_true(g_value_get_object(&events[1]->any_data) == bar.get() || g_value_get_object(&events[1]->any_data) == baz.get()); >+ g_assert_true(g_value_get_object(&events[0]->any_data) != g_value_get_object(&events[1]->any_data)); >+ set = adoptGRef(atspi_accessible_get_state_set(bar.get())); >+ g_assert_true(atspi_state_set_contains(set.get(), ATSPI_STATE_DEFUNCT)); >+ set = adoptGRef(atspi_accessible_get_state_set(baz.get())); >+ g_assert_true(atspi_state_set_contains(set.get(), ATSPI_STATE_DEFUNCT)); >+ g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 0); >+#endif >+} >+ >+static void testAccessibleAttributes(AccessibilityTest* test, gconstpointer) >+{ >+ test->showInWindow(); >+ test->loadHtml( >+ "<html>" >+ " <body>" >+ " <h2>Heading level 2</h2>" >+ " <a id=\"webkitgtk\" href=\"http://www.webkitgtk.org\">A link</a>" >+ " </body>" >+ "</html>", >+ nullptr); >+ test->waitUntilLoadFinished(); >+ >+#if USE(ATSPI) >+ static const char* toolkitName = "WebKitGTK"; >+#else >+ static const char* toolkitName = "WebKitGtk"; >+#endif >+ >+ auto testApp = test->findTestApplication(); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(testApp.get())); >+ >+ auto documentWeb = test->findDocumentWeb(testApp.get()); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(documentWeb.get())); >+ g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 2); >+ auto attributes = adoptGRef(atspi_accessible_get_attributes(documentWeb.get(), nullptr)); >+#if USE(ATSPI) >+ g_assert_cmpuint(g_hash_table_size(attributes.get()), ==, 1); >+#else >+ // FIXME: ATK includes dragged state, but dragging is not supported. >+ g_assert_cmpuint(g_hash_table_size(attributes.get()), ==, 2); >+#endif >+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "toolkit")), ==, toolkitName); >+ >+ auto h2 = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(h2.get())); >+ attributes = adoptGRef(atspi_accessible_get_attributes(h2.get(), nullptr)); >+ g_assert_cmpuint(g_hash_table_size(attributes.get()), ==, 4); >+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "toolkit")), ==, toolkitName); >+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "computed-role")), ==, "heading"); >+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "level")), ==, "2"); >+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "tag")), ==, "h2"); >+ >+ auto section = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 1, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(section.get())); >+ g_assert_cmpint(atspi_accessible_get_child_count(section.get(), nullptr), ==, 1); >+ >+ auto a = adoptGRef(atspi_accessible_get_child_at_index(section.get(), 0, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(a.get())); >+ attributes = adoptGRef(atspi_accessible_get_attributes(a.get(), nullptr)); >+ g_assert_cmpuint(g_hash_table_size(attributes.get()), ==, 4); >+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "toolkit")), ==, toolkitName); >+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "computed-role")), ==, "link"); >+#if USE(ATSPI) >+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "id")), ==, "webkitgtk"); >+#else >+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "html-id")), ==, "webkitgtk"); >+#endif >+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "tag")), ==, "a"); >+} >+ >+static void testAccessibleState(AccessibilityTest* test, gconstpointer) >+{ >+ test->showInWindow(800, 600); >+ test->loadHtml( >+ "<html>" >+ " <body>" >+ " <h1>State</h1>" >+ " <button>Button</button>" >+ " <button disabled>Disabled</button>" >+ " <button aria-pressed='true'>Toggle button</button>" >+ " <input type='radio' name='radio'/>Radio" >+ " <input type='radio' name='radio' checked/>Radio Checked" >+ " <input type='radio' name='radio' disabled/>Radio disabled" >+ " <input type='checkbox' name='check-disabled' checked disabled/>Checked Disabled" >+ " <input value='Entry' aria-required='true'>" >+ " <input value='Disabled' disabled>" >+ " <input value='Read only' readonly>" >+ " <textarea rows=5 autofocus></textarea>" >+ " </body>" >+ "</html>", >+ nullptr); >+ test->waitUntilLoadFinished(); >+ >+ auto testApp = test->findTestApplication(); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(testApp.get())); >+ >+ auto documentWeb = test->findDocumentWeb(testApp.get()); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(documentWeb.get())); >+ g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 2); >+ >+ auto h1 = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(h1.get())); >+ GRefPtr<AtspiStateSet> stateSet = adoptGRef(atspi_accessible_get_state_set(h1.get())); >+#if USE(ATSPI) >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 4); >+#else >+ // FIXME: ATK includes orientation state in every element. >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 5); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_HORIZONTAL)); >+#endif >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_ENABLED)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SENSITIVE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_VISIBLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SHOWING)); >+ >+ auto section = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 1, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(section.get())); >+ g_assert_cmpint(atspi_accessible_get_child_count(section.get(), nullptr), ==, 11); >+ >+ unsigned nextChild = 0; >+ auto button = adoptGRef(atspi_accessible_get_child_at_index(section.get(), nextChild++, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(button.get())); >+ stateSet = adoptGRef(atspi_accessible_get_state_set(button.get())); >+#if USE(ATSPI) >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 5); >+#else >+ // FIXME: ATK includes orientation state in every element. >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 6); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_HORIZONTAL)); >+#endif >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_ENABLED)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SENSITIVE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_VISIBLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SHOWING)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_FOCUSABLE)); >+ >+ auto buttonDisabled = adoptGRef(atspi_accessible_get_child_at_index(section.get(), nextChild++, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(buttonDisabled.get())); >+ stateSet = adoptGRef(atspi_accessible_get_state_set(buttonDisabled.get())); >+#if USE(ATSPI) >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 2); >+ // FIXME: ATK includes orientation state in every element. >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 3); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_HORIZONTAL)); >+#endif >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_VISIBLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SHOWING)); >+ >+ auto toggleButton = adoptGRef(atspi_accessible_get_child_at_index(section.get(), nextChild++, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(toggleButton.get())); >+ stateSet = adoptGRef(atspi_accessible_get_state_set(toggleButton.get())); >+#if USE(ATSPI) >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 6); >+#else >+ // FIXME: ATK includes orientation state in every element. >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 7); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_HORIZONTAL)); >+#endif >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_ENABLED)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SENSITIVE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_VISIBLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SHOWING)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_FOCUSABLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_PRESSED)); >+ >+ auto radio = adoptGRef(atspi_accessible_get_child_at_index(section.get(), nextChild++, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(radio.get())); >+ stateSet = adoptGRef(atspi_accessible_get_state_set(radio.get())); >+#if USE(ATSPI) >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 6); >+#else >+ // FIXME: ATK includes orientation state in every element. >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 7); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_VERTICAL)); >+#endif >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_ENABLED)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SENSITIVE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_VISIBLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SHOWING)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_FOCUSABLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_CHECKABLE)); >+ >+ auto radioChecked = adoptGRef(atspi_accessible_get_child_at_index(section.get(), nextChild++, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(radioChecked.get())); >+ stateSet = adoptGRef(atspi_accessible_get_state_set(radioChecked.get())); >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 7); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_ENABLED)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SENSITIVE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_VISIBLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SHOWING)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_FOCUSABLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_CHECKABLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_CHECKED)); >+ >+ auto radioDisabled = adoptGRef(atspi_accessible_get_child_at_index(section.get(), nextChild++, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(radioDisabled.get())); >+ stateSet = adoptGRef(atspi_accessible_get_state_set(radioDisabled.get())); >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 3); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_VISIBLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SHOWING)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_CHECKABLE)); >+ >+ auto checkbox = adoptGRef(atspi_accessible_get_child_at_index(section.get(), nextChild++, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(checkbox.get())); >+ stateSet = adoptGRef(atspi_accessible_get_state_set(checkbox.get())); >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 4); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_VISIBLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SHOWING)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_CHECKABLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_CHECKED)); >+ >+ auto entry = adoptGRef(atspi_accessible_get_child_at_index(section.get(), nextChild++, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(entry.get())); >+ stateSet = adoptGRef(atspi_accessible_get_state_set(entry.get())); >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 9); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_ENABLED)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SENSITIVE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_VISIBLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SHOWING)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_FOCUSABLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_EDITABLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SINGLE_LINE)); >+#if USE(ATSPI) >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SELECTABLE_TEXT)); >+#else >+ // FIXME: ATK doesn't implement selectable text, but includes oprientation. >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_HORIZONTAL)); >+#endif >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_REQUIRED)); >+ >+ auto entryDisabled = adoptGRef(atspi_accessible_get_child_at_index(section.get(), nextChild++, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(entryDisabled.get())); >+ stateSet = adoptGRef(atspi_accessible_get_state_set(entryDisabled.get())); >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 5); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_VISIBLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SHOWING)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_EDITABLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SINGLE_LINE)); >+#if USE(ATSPI) >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SELECTABLE_TEXT)); >+#else >+ // FIXME: ATK doesn't implement selectable text, but includes oprientation. >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_HORIZONTAL)); >+#endif >+ >+ auto entryReadOnly = adoptGRef(atspi_accessible_get_child_at_index(section.get(), nextChild++, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(entryReadOnly.get())); >+ stateSet = adoptGRef(atspi_accessible_get_state_set(entryReadOnly.get())); >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 8); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_ENABLED)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SENSITIVE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_VISIBLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SHOWING)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_FOCUSABLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SINGLE_LINE)); >+#if USE(ATSPI) >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SELECTABLE_TEXT)); >+#else >+ // FIXME: ATK doesn't implement selectable text, but includes oprientation. >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_HORIZONTAL)); >+#endif >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_READ_ONLY)); >+ >+ auto textArea = adoptGRef(atspi_accessible_get_child_at_index(section.get(), nextChild++, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(textArea.get())); >+ stateSet = adoptGRef(atspi_accessible_get_state_set(textArea.get())); >+ g_assert_cmpuint(AccessibilityTest::stateSetSize(stateSet.get()), ==, 9); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_ENABLED)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SENSITIVE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_VISIBLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SHOWING)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_FOCUSABLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_FOCUSED)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_EDITABLE)); >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_MULTI_LINE)); >+#if USE(ATSPI) >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_SELECTABLE_TEXT)); >+#else >+ // FIXME: ATK doesn't implement selectable text, but includes oprientation. >+ g_assert_true(atspi_state_set_contains(stateSet.get(), ATSPI_STATE_HORIZONTAL)); >+#endif >+} >+ >+static void testAccessibleStateChanged(AccessibilityTest* test, gconstpointer) >+{ >+ test->showInWindow(); >+ test->loadHtml( >+ "<html>" >+ " <body>" >+ " <input id='check' type='checkbox'/>Checkbox" >+ " <button id='toggle' aria-pressed='true'>Toggle button</button>" >+ " <input id='entry' value='Entry'>" >+ " </body>" >+ "</html>", >+ nullptr); >+ test->waitUntilLoadFinished(); >+ >+ auto testApp = test->findTestApplication(); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(testApp.get())); >+ >+ auto documentWeb = test->findDocumentWeb(testApp.get()); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(documentWeb.get())); >+ g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 1); >+ >+ auto section = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(section.get())); >+ g_assert_cmpint(atspi_accessible_get_child_count(section.get(), nullptr), ==, 3); >+ >+ unsigned nextChild = 0; >+ auto checkbox = adoptGRef(atspi_accessible_get_child_at_index(section.get(), nextChild++, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(checkbox.get())); >+ test->startEventMonitor(checkbox.get(), { "object:state-changed" }); >+ test->runJavaScriptAndWaitUntilFinished("document.getElementById('check').checked = true;", nullptr); >+ auto events = test->stopEventMonitor(1); >+ g_assert_cmpuint(events.size(), ==, 1); >+ g_assert_cmpstr(events[0]->type, ==, "object:state-changed:checked"); >+ g_assert_cmpuint(events[0]->detail1, ==, 1); >+ events = { }; >+ >+ auto toggleButton = adoptGRef(atspi_accessible_get_child_at_index(section.get(), nextChild++, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(toggleButton.get())); >+ test->startEventMonitor(toggleButton.get(), { "object:state-changed" }); >+ test->runJavaScriptAndWaitUntilFinished("document.getElementById('toggle').ariaPressed = false;", nullptr); >+ events = test->stopEventMonitor(1); >+ g_assert_cmpuint(events.size(), ==, 1); >+ g_assert_cmpstr(events[0]->type, ==, "object:state-changed:pressed"); >+ g_assert_cmpuint(events[0]->detail1, ==, 0); >+ events = { }; >+ >+ auto entry = adoptGRef(atspi_accessible_get_child_at_index(section.get(), nextChild++, nullptr)); >+ g_assert_true(ATSPI_IS_ACCESSIBLE(entry.get())); >+ test->startEventMonitor(entry.get(), { "object:state-changed" }); >+ test->runJavaScriptAndWaitUntilFinished("let e = document.getElementById('entry'); e.ariaRequired = true; e.focus();", nullptr); >+ events = test->stopEventMonitor(2); >+#if !USE(ATSPI) >+ // FIXME: With ATK focused event is sometimes emitted twice for some reason. >+ if (events.size() == 3) { >+ events.removeFirstMatching([](auto& item) { >+ if (!strcmp(item->type, "object:state-changed:focused")) >+ return true; >+ return false; >+ }); >+ } >+#endif >+ g_assert_cmpuint(events.size(), ==, 2); >+ if (!g_strcmp0(events[0]->type, "object:state-changed:focused")) { >+ g_assert_cmpuint(events[0]->detail1, ==, 1); >+ >+ g_assert_cmpstr(events[1]->type, ==, "object:state-changed:required"); >+ g_assert_cmpuint(events[1]->detail1, ==, 1); >+ } else if (!g_strcmp0(events[0]->type, "object:state-changed:required")) { >+ g_assert_cmpuint(events[0]->detail1, ==, 1); >+ >+ g_assert_cmpstr(events[1]->type, ==, "object:state-changed:focused"); >+ g_assert_cmpuint(events[1]->detail1, ==, 1); >+ } else >+ g_assert_not_reached(); >+ events = { }; >+} >+ > void beforeAll() > { >- AccessibilityTest::add("WebKitAccessibility", "atspi-basic-hierarchy", testAtspiBasicHierarchy); >+ AccessibilityTest::add("WebKitAccessibility", "accessible/basic-hierarchy", testAccessibleBasicHierarchy); >+ AccessibilityTest::add("WebKitAccessibility", "accessible/ignored-objects", testAccessibleIgnoredObjects); >+ AccessibilityTest::add("WebKitAccessibility", "accessible/children-changed", testAccessibleChildrenChanged); >+ AccessibilityTest::add("WebKitAccessibility", "accessible/attributes", testAccessibleAttributes); >+ AccessibilityTest::add("WebKitAccessibility", "accessible/state", testAccessibleState); >+ AccessibilityTest::add("WebKitAccessibility", "accessible/state-changed", testAccessibleStateChanged); > } > > void afterAll()
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Flags:
aperez
:
review+
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 230256
:
439730
| 439831