diff options
Diffstat (limited to '')
30 files changed, 5023 insertions, 0 deletions
diff --git a/accessible/atk/AccessibleWrap.cpp b/accessible/atk/AccessibleWrap.cpp new file mode 100644 index 0000000000..98f303ee4a --- /dev/null +++ b/accessible/atk/AccessibleWrap.cpp @@ -0,0 +1,1305 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AccessibleWrap.h" + +#include "LocalAccessible-inl.h" +#include "AccAttributes.h" +#include "ApplicationAccessibleWrap.h" +#include "InterfaceInitFuncs.h" +#include "nsAccUtils.h" +#include "mozilla/a11y/PDocAccessible.h" +#include "OuterDocAccessible.h" +#include "RemoteAccessible.h" +#include "DocAccessibleParent.h" +#include "RootAccessible.h" +#include "mozilla/a11y/TableAccessible.h" +#include "mozilla/a11y/TableCellAccessible.h" +#include "nsMai.h" +#include "nsMaiHyperlink.h" +#include "nsString.h" +#include "nsStateMap.h" +#include "mozilla/a11y/Platform.h" +#include "Relation.h" +#include "RootAccessible.h" +#include "States.h" +#include "nsISimpleEnumerator.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Sprintf.h" +#include "nsAccessibilityService.h" +#include "nsComponentManagerUtils.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +MaiAtkObject::EAvailableAtkSignals MaiAtkObject::gAvailableAtkSignals = + eUnknown; + +// defined in ApplicationAccessibleWrap.cpp +extern "C" GType g_atk_hyperlink_impl_type; + +/* MaiAtkObject */ + +enum { + ACTIVATE, + CREATE, + DEACTIVATE, + DESTROY, + MAXIMIZE, + MINIMIZE, + RESIZE, + RESTORE, + LAST_SIGNAL +}; + +enum MaiInterfaceType { + MAI_INTERFACE_COMPONENT, /* 0 */ + MAI_INTERFACE_ACTION, + MAI_INTERFACE_VALUE, + MAI_INTERFACE_EDITABLE_TEXT, + MAI_INTERFACE_HYPERTEXT, + MAI_INTERFACE_HYPERLINK_IMPL, + MAI_INTERFACE_SELECTION, + MAI_INTERFACE_TABLE, + MAI_INTERFACE_TEXT, + MAI_INTERFACE_DOCUMENT, + MAI_INTERFACE_IMAGE, /* 10 */ + MAI_INTERFACE_TABLE_CELL +}; + +static GType GetAtkTypeForMai(MaiInterfaceType type) { + switch (type) { + case MAI_INTERFACE_COMPONENT: + return ATK_TYPE_COMPONENT; + case MAI_INTERFACE_ACTION: + return ATK_TYPE_ACTION; + case MAI_INTERFACE_VALUE: + return ATK_TYPE_VALUE; + case MAI_INTERFACE_EDITABLE_TEXT: + return ATK_TYPE_EDITABLE_TEXT; + case MAI_INTERFACE_HYPERTEXT: + return ATK_TYPE_HYPERTEXT; + case MAI_INTERFACE_HYPERLINK_IMPL: + return g_atk_hyperlink_impl_type; + case MAI_INTERFACE_SELECTION: + return ATK_TYPE_SELECTION; + case MAI_INTERFACE_TABLE: + return ATK_TYPE_TABLE; + case MAI_INTERFACE_TEXT: + return ATK_TYPE_TEXT; + case MAI_INTERFACE_DOCUMENT: + return ATK_TYPE_DOCUMENT; + case MAI_INTERFACE_IMAGE: + return ATK_TYPE_IMAGE; + case MAI_INTERFACE_TABLE_CELL: + MOZ_ASSERT(false); + } + return G_TYPE_INVALID; +} + +#define NON_USER_EVENT ":system" + +// The atk interfaces we can expose without checking what version of ATK we are +// dealing with. At the moment AtkTableCell is the only interface we can't +// always expose. +static const GInterfaceInfo atk_if_infos[] = { + {(GInterfaceInitFunc)componentInterfaceInitCB, + (GInterfaceFinalizeFunc) nullptr, nullptr}, + {(GInterfaceInitFunc)actionInterfaceInitCB, + (GInterfaceFinalizeFunc) nullptr, nullptr}, + {(GInterfaceInitFunc)valueInterfaceInitCB, (GInterfaceFinalizeFunc) nullptr, + nullptr}, + {(GInterfaceInitFunc)editableTextInterfaceInitCB, + (GInterfaceFinalizeFunc) nullptr, nullptr}, + {(GInterfaceInitFunc)hypertextInterfaceInitCB, + (GInterfaceFinalizeFunc) nullptr, nullptr}, + {(GInterfaceInitFunc)hyperlinkImplInterfaceInitCB, + (GInterfaceFinalizeFunc) nullptr, nullptr}, + {(GInterfaceInitFunc)selectionInterfaceInitCB, + (GInterfaceFinalizeFunc) nullptr, nullptr}, + {(GInterfaceInitFunc)tableInterfaceInitCB, (GInterfaceFinalizeFunc) nullptr, + nullptr}, + {(GInterfaceInitFunc)textInterfaceInitCB, (GInterfaceFinalizeFunc) nullptr, + nullptr}, + {(GInterfaceInitFunc)documentInterfaceInitCB, + (GInterfaceFinalizeFunc) nullptr, nullptr}, + {(GInterfaceInitFunc)imageInterfaceInitCB, (GInterfaceFinalizeFunc) nullptr, + nullptr}}; + +static GQuark quark_mai_hyperlink = 0; + +AtkHyperlink* MaiAtkObject::GetAtkHyperlink() { + NS_ASSERTION(quark_mai_hyperlink, "quark_mai_hyperlink not initialized"); + MaiHyperlink* maiHyperlink = + (MaiHyperlink*)g_object_get_qdata(G_OBJECT(this), quark_mai_hyperlink); + if (!maiHyperlink) { + maiHyperlink = new MaiHyperlink(acc); + g_object_set_qdata(G_OBJECT(this), quark_mai_hyperlink, maiHyperlink); + } + + return maiHyperlink->GetAtkHyperlink(); +} + +void MaiAtkObject::Shutdown() { + acc = nullptr; + MaiHyperlink* maiHyperlink = + (MaiHyperlink*)g_object_get_qdata(G_OBJECT(this), quark_mai_hyperlink); + if (maiHyperlink) { + delete maiHyperlink; + g_object_set_qdata(G_OBJECT(this), quark_mai_hyperlink, nullptr); + } +} + +struct MaiAtkObjectClass { + AtkObjectClass parent_class; +}; + +static guint mai_atk_object_signals[LAST_SIGNAL] = { + 0, +}; + +static void MaybeFireNameChange(AtkObject* aAtkObj, const nsString& aNewName); + +G_BEGIN_DECLS +/* callbacks for MaiAtkObject */ +static void classInitCB(AtkObjectClass* aClass); +static void initializeCB(AtkObject* aAtkObj, gpointer aData); +static void finalizeCB(GObject* aObj); + +/* callbacks for AtkObject virtual functions */ +static const gchar* getNameCB(AtkObject* aAtkObj); +/* getDescriptionCB is also used by image interface */ +const gchar* getDescriptionCB(AtkObject* aAtkObj); +static AtkRole getRoleCB(AtkObject* aAtkObj); +static AtkAttributeSet* getAttributesCB(AtkObject* aAtkObj); +static const gchar* GetLocaleCB(AtkObject*); +static AtkObject* getParentCB(AtkObject* aAtkObj); +static gint getChildCountCB(AtkObject* aAtkObj); +static AtkObject* refChildCB(AtkObject* aAtkObj, gint aChildIndex); +static gint getIndexInParentCB(AtkObject* aAtkObj); +static AtkStateSet* refStateSetCB(AtkObject* aAtkObj); +static AtkRelationSet* refRelationSetCB(AtkObject* aAtkObj); + +/* the missing atkobject virtual functions */ +/* + static AtkLayer getLayerCB(AtkObject *aAtkObj); + static gint getMdiZorderCB(AtkObject *aAtkObj); + static void SetNameCB(AtkObject *aAtkObj, + const gchar *name); + static void SetDescriptionCB(AtkObject *aAtkObj, + const gchar *description); + static void SetParentCB(AtkObject *aAtkObj, + AtkObject *parent); + static void SetRoleCB(AtkObject *aAtkObj, + AtkRole role); + static guint ConnectPropertyChangeHandlerCB( + AtkObject *aObj, + AtkPropertyChangeHandler *handler); + static void RemovePropertyChangeHandlerCB( + AtkObject *aAtkObj, + guint handler_id); + static void InitializeCB(AtkObject *aAtkObj, + gpointer data); + static void ChildrenChangedCB(AtkObject *aAtkObj, + guint change_index, + gpointer changed_child); + static void FocusEventCB(AtkObject *aAtkObj, + gboolean focus_in); + static void PropertyChangeCB(AtkObject *aAtkObj, + AtkPropertyValues *values); + static void StateChangeCB(AtkObject *aAtkObj, + const gchar *name, + gboolean state_set); + static void VisibleDataChangedCB(AtkObject *aAtkObj); +*/ +G_END_DECLS + +static GType GetMaiAtkType(uint16_t interfacesBits); +static const char* GetUniqueMaiAtkTypeName(uint16_t interfacesBits); + +static gpointer parent_class = nullptr; + +GType mai_atk_object_get_type(void) { + static GType type = 0; + + if (!type) { + static const GTypeInfo tinfo = { + sizeof(MaiAtkObjectClass), + (GBaseInitFunc) nullptr, + (GBaseFinalizeFunc) nullptr, + (GClassInitFunc)classInitCB, + (GClassFinalizeFunc) nullptr, + nullptr, /* class data */ + sizeof(MaiAtkObject), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc) nullptr, + nullptr /* value table */ + }; + + type = g_type_register_static(ATK_TYPE_OBJECT, "MaiAtkObject", &tinfo, + GTypeFlags(0)); + quark_mai_hyperlink = g_quark_from_static_string("MaiHyperlink"); + } + return type; +} + +AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) + : LocalAccessible(aContent, aDoc), mAtkObject(nullptr) {} + +AccessibleWrap::~AccessibleWrap() { + NS_ASSERTION(!mAtkObject, "ShutdownAtkObject() is not called"); +} + +void AccessibleWrap::ShutdownAtkObject() { + if (!mAtkObject) return; + + NS_ASSERTION(IS_MAI_OBJECT(mAtkObject), "wrong type of atk object"); + if (IS_MAI_OBJECT(mAtkObject)) MAI_ATK_OBJECT(mAtkObject)->Shutdown(); + + g_object_unref(mAtkObject); + mAtkObject = nullptr; +} + +void AccessibleWrap::Shutdown() { + ShutdownAtkObject(); + LocalAccessible::Shutdown(); +} + +void AccessibleWrap::GetNativeInterface(void** aOutAccessible) { + *aOutAccessible = nullptr; + + if (!mAtkObject) { + if (IsDefunct() || IsText()) { + // We don't create ATK objects for node which has been shutdown or + // plain text leaves + return; + } + + GType type = GetMaiAtkType(CreateMaiInterfaces()); + if (!type) return; + + mAtkObject = reinterpret_cast<AtkObject*>(g_object_new(type, nullptr)); + if (!mAtkObject) return; + + atk_object_initialize(mAtkObject, static_cast<Accessible*>(this)); + mAtkObject->role = ATK_ROLE_INVALID; + mAtkObject->layer = ATK_LAYER_INVALID; + } + + *aOutAccessible = mAtkObject; +} + +AtkObject* AccessibleWrap::GetAtkObject(void) { + void* atkObj = nullptr; + GetNativeInterface(&atkObj); + return static_cast<AtkObject*>(atkObj); +} + +// Get AtkObject from LocalAccessible interface +/* static */ +AtkObject* AccessibleWrap::GetAtkObject(LocalAccessible* acc) { + void* atkObjPtr = nullptr; + acc->GetNativeInterface(&atkObjPtr); + return atkObjPtr ? ATK_OBJECT(atkObjPtr) : nullptr; +} + +/* private */ +uint16_t AccessibleWrap::CreateMaiInterfaces(void) { + uint16_t interfacesBits = 0; + + // The Component interface is supported by all accessibles. + interfacesBits |= 1 << MAI_INTERFACE_COMPONENT; + + // Add Action interface if the action count is more than zero. + if (ActionCount() > 0) interfacesBits |= 1 << MAI_INTERFACE_ACTION; + + // Text, Editabletext, and Hypertext interface. + HyperTextAccessible* hyperText = AsHyperText(); + if (hyperText && hyperText->IsTextRole()) { + interfacesBits |= 1 << MAI_INTERFACE_TEXT; + interfacesBits |= 1 << MAI_INTERFACE_EDITABLE_TEXT; + if (!nsAccUtils::MustPrune(this)) { + interfacesBits |= 1 << MAI_INTERFACE_HYPERTEXT; + } + } + + // Value interface. + if (HasNumericValue()) interfacesBits |= 1 << MAI_INTERFACE_VALUE; + + // Document interface. + if (IsDoc()) interfacesBits |= 1 << MAI_INTERFACE_DOCUMENT; + + if (IsImage()) interfacesBits |= 1 << MAI_INTERFACE_IMAGE; + + // HyperLink interface. + if (IsLink()) interfacesBits |= 1 << MAI_INTERFACE_HYPERLINK_IMPL; + + if (!nsAccUtils::MustPrune(this)) { // These interfaces require children + // Table interface. + if (AsTable()) interfacesBits |= 1 << MAI_INTERFACE_TABLE; + + if (AsTableCell()) interfacesBits |= 1 << MAI_INTERFACE_TABLE_CELL; + + // Selection interface. + if (IsSelect()) { + interfacesBits |= 1 << MAI_INTERFACE_SELECTION; + } + } + + return interfacesBits; +} + +static GType GetMaiAtkType(uint16_t interfacesBits) { + GType type; + static const GTypeInfo tinfo = { + sizeof(MaiAtkObjectClass), + (GBaseInitFunc) nullptr, + (GBaseFinalizeFunc) nullptr, + (GClassInitFunc) nullptr, + (GClassFinalizeFunc) nullptr, + nullptr, /* class data */ + sizeof(MaiAtkObject), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc) nullptr, + nullptr /* value table */ + }; + + /* + * The members we use to register GTypes are GetAtkTypeForMai + * and atk_if_infos, which are constant values to each MaiInterface + * So we can reuse the registered GType when having + * the same MaiInterface types. + */ + const char* atkTypeName = GetUniqueMaiAtkTypeName(interfacesBits); + type = g_type_from_name(atkTypeName); + if (type) { + return type; + } + + /* + * gobject limits the number of types that can directly derive from any + * given object type to 4095. + */ + static uint16_t typeRegCount = 0; + if (typeRegCount++ >= 4095) { + return G_TYPE_INVALID; + } + type = g_type_register_static(MAI_TYPE_ATK_OBJECT, atkTypeName, &tinfo, + GTypeFlags(0)); + + for (uint32_t index = 0; index < ArrayLength(atk_if_infos); index++) { + if (interfacesBits & (1 << index)) { + g_type_add_interface_static(type, + GetAtkTypeForMai((MaiInterfaceType)index), + &atk_if_infos[index]); + } + } + + // Special case AtkTableCell so we can check what version of Atk we are + // dealing with. + if (IsAtkVersionAtLeast(2, 12) && + (interfacesBits & (1 << MAI_INTERFACE_TABLE_CELL))) { + const GInterfaceInfo cellInfo = { + (GInterfaceInitFunc)tableCellInterfaceInitCB, + (GInterfaceFinalizeFunc) nullptr, nullptr}; + g_type_add_interface_static(type, gAtkTableCellGetTypeFunc(), &cellInfo); + } + + return type; +} + +static const char* GetUniqueMaiAtkTypeName(uint16_t interfacesBits) { +#define MAI_ATK_TYPE_NAME_LEN (30) /* 10+sizeof(uint16_t)*8/4+1 < 30 */ + + static gchar namePrefix[] = "MaiAtkType"; /* size = 10 */ + static gchar name[MAI_ATK_TYPE_NAME_LEN + 1]; + + SprintfLiteral(name, "%s%x", namePrefix, interfacesBits); + name[MAI_ATK_TYPE_NAME_LEN] = '\0'; + + return name; +} + +bool AccessibleWrap::IsValidObject() { + // to ensure we are not shut down + return !IsDefunct(); +} + +/* static functions for ATK callbacks */ +void classInitCB(AtkObjectClass* aClass) { + GObjectClass* gobject_class = G_OBJECT_CLASS(aClass); + + parent_class = g_type_class_peek_parent(aClass); + + aClass->get_name = getNameCB; + aClass->get_description = getDescriptionCB; + aClass->get_parent = getParentCB; + aClass->get_n_children = getChildCountCB; + aClass->ref_child = refChildCB; + aClass->get_index_in_parent = getIndexInParentCB; + aClass->get_role = getRoleCB; + aClass->get_attributes = getAttributesCB; + aClass->get_object_locale = GetLocaleCB; + aClass->ref_state_set = refStateSetCB; + aClass->ref_relation_set = refRelationSetCB; + + aClass->initialize = initializeCB; + + gobject_class->finalize = finalizeCB; + + mai_atk_object_signals[ACTIVATE] = g_signal_new( + "activate", MAI_TYPE_ATK_OBJECT, G_SIGNAL_RUN_LAST, + 0, /* default signal handler */ + nullptr, nullptr, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + mai_atk_object_signals[CREATE] = g_signal_new( + "create", MAI_TYPE_ATK_OBJECT, G_SIGNAL_RUN_LAST, + 0, /* default signal handler */ + nullptr, nullptr, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + mai_atk_object_signals[DEACTIVATE] = g_signal_new( + "deactivate", MAI_TYPE_ATK_OBJECT, G_SIGNAL_RUN_LAST, + 0, /* default signal handler */ + nullptr, nullptr, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + mai_atk_object_signals[DESTROY] = g_signal_new( + "destroy", MAI_TYPE_ATK_OBJECT, G_SIGNAL_RUN_LAST, + 0, /* default signal handler */ + nullptr, nullptr, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + mai_atk_object_signals[MAXIMIZE] = g_signal_new( + "maximize", MAI_TYPE_ATK_OBJECT, G_SIGNAL_RUN_LAST, + 0, /* default signal handler */ + nullptr, nullptr, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + mai_atk_object_signals[MINIMIZE] = g_signal_new( + "minimize", MAI_TYPE_ATK_OBJECT, G_SIGNAL_RUN_LAST, + 0, /* default signal handler */ + nullptr, nullptr, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + mai_atk_object_signals[RESIZE] = g_signal_new( + "resize", MAI_TYPE_ATK_OBJECT, G_SIGNAL_RUN_LAST, + 0, /* default signal handler */ + nullptr, nullptr, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + mai_atk_object_signals[RESTORE] = g_signal_new( + "restore", MAI_TYPE_ATK_OBJECT, G_SIGNAL_RUN_LAST, + 0, /* default signal handler */ + nullptr, nullptr, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); +} + +void initializeCB(AtkObject* aAtkObj, gpointer aData) { + NS_ASSERTION((IS_MAI_OBJECT(aAtkObj)), "Invalid AtkObject"); + NS_ASSERTION(aData, "Invalid Data to init AtkObject"); + if (!aAtkObj || !aData) return; + + /* call parent init function */ + /* AtkObjectClass has not a "initialize" function now, + * maybe it has later + */ + + if (ATK_OBJECT_CLASS(parent_class)->initialize) { + ATK_OBJECT_CLASS(parent_class)->initialize(aAtkObj, aData); + } + + /* initialize object */ + MAI_ATK_OBJECT(aAtkObj)->acc = static_cast<Accessible*>(aData); +} + +void finalizeCB(GObject* aObj) { + if (!IS_MAI_OBJECT(aObj)) return; + NS_ASSERTION(!MAI_ATK_OBJECT(aObj)->acc, "acc NOT null"); + + // call parent finalize function + // finalize of GObjectClass will unref the accessible parent if has + if (G_OBJECT_CLASS(parent_class)->finalize) { + G_OBJECT_CLASS(parent_class)->finalize(aObj); + } +} + +const gchar* getNameCB(AtkObject* aAtkObj) { + nsAutoString name; + if (Accessible* acc = GetInternalObj(aAtkObj)) { + acc->Name(name); + } else { + return nullptr; + } + + // XXX Firing an event from here does not seem right + MaybeFireNameChange(aAtkObj, name); + + return aAtkObj->name; +} + +static void MaybeFireNameChange(AtkObject* aAtkObj, const nsString& aNewName) { + NS_ConvertUTF16toUTF8 newNameUTF8(aNewName); + if (aAtkObj->name && !strcmp(aAtkObj->name, newNameUTF8.get())) return; + + // Below we duplicate the functionality of atk_object_set_name(), + // but without calling atk_object_get_name(). Instead of + // atk_object_get_name() we directly access aAtkObj->name. This is because + // atk_object_get_name() would call getNameCB() which would call + // MaybeFireNameChange() (or atk_object_set_name() before this problem was + // fixed) and we would get an infinite recursion. + // See http://bugzilla.mozilla.org/733712 + + // Do not notify for initial name setting. + // See bug http://bugzilla.gnome.org/665870 + bool notify = !!aAtkObj->name; + + free(aAtkObj->name); + aAtkObj->name = strdup(newNameUTF8.get()); + + if (notify) g_object_notify(G_OBJECT(aAtkObj), "accessible-name"); +} + +const gchar* getDescriptionCB(AtkObject* aAtkObj) { + nsAutoString uniDesc; + if (Accessible* acc = GetInternalObj(aAtkObj)) { + acc->Description(uniDesc); + } else { + return nullptr; + } + + NS_ConvertUTF8toUTF16 objDesc(aAtkObj->description); + if (!uniDesc.Equals(objDesc)) { + atk_object_set_description(aAtkObj, NS_ConvertUTF16toUTF8(uniDesc).get()); + } + + return aAtkObj->description; +} + +AtkRole getRoleCB(AtkObject* aAtkObj) { + if (aAtkObj->role != ATK_ROLE_INVALID) return aAtkObj->role; + + Accessible* acc = GetInternalObj(aAtkObj); + if (!acc) { + return ATK_ROLE_INVALID; + } + +#ifdef DEBUG + if (AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj)) { + NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(accWrap), + "Does not support Text interface when it should"); + } +#endif + +#define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \ + msaaRole, ia2Role, androidClass, nameRule) \ + case roles::geckoRole: \ + aAtkObj->role = atkRole; \ + break; + + switch (acc->Role()) { +#include "RoleMap.h" + default: + MOZ_CRASH("Unknown role."); + } + +#undef ROLE + + if (aAtkObj->role == ATK_ROLE_LIST_BOX && !IsAtkVersionAtLeast(2, 1)) { + aAtkObj->role = ATK_ROLE_LIST; + } else if (aAtkObj->role == ATK_ROLE_TABLE_ROW && + !IsAtkVersionAtLeast(2, 1)) { + aAtkObj->role = ATK_ROLE_LIST_ITEM; + } else if (aAtkObj->role == ATK_ROLE_MATH && !IsAtkVersionAtLeast(2, 12)) { + aAtkObj->role = ATK_ROLE_SECTION; + } else if (aAtkObj->role == ATK_ROLE_COMMENT && !IsAtkVersionAtLeast(2, 12)) { + aAtkObj->role = ATK_ROLE_SECTION; + } else if (aAtkObj->role == ATK_ROLE_LANDMARK && + !IsAtkVersionAtLeast(2, 12)) { + aAtkObj->role = ATK_ROLE_SECTION; + } else if (aAtkObj->role == ATK_ROLE_FOOTNOTE && + !IsAtkVersionAtLeast(2, 25, 2)) { + aAtkObj->role = ATK_ROLE_SECTION; + } else if (aAtkObj->role == ATK_ROLE_STATIC && !IsAtkVersionAtLeast(2, 16)) { + aAtkObj->role = ATK_ROLE_TEXT; + } else if ((aAtkObj->role == ATK_ROLE_MATH_FRACTION || + aAtkObj->role == ATK_ROLE_MATH_ROOT) && + !IsAtkVersionAtLeast(2, 16)) { + aAtkObj->role = ATK_ROLE_SECTION; + } else if (aAtkObj->role == ATK_ROLE_MARK && !IsAtkVersionAtLeast(2, 36)) { + aAtkObj->role = ATK_ROLE_TEXT; + } else if (aAtkObj->role == ATK_ROLE_SUGGESTION && + !IsAtkVersionAtLeast(2, 36)) { + aAtkObj->role = ATK_ROLE_SECTION; + } else if (aAtkObj->role == ATK_ROLE_COMMENT && !IsAtkVersionAtLeast(2, 36)) { + aAtkObj->role = ATK_ROLE_SECTION; + } else if ((aAtkObj->role == ATK_ROLE_CONTENT_DELETION || + aAtkObj->role == ATK_ROLE_CONTENT_INSERTION) && + !IsAtkVersionAtLeast(2, 34)) { + aAtkObj->role = ATK_ROLE_SECTION; + } + + return aAtkObj->role; +} + +static AtkAttributeSet* ConvertToAtkAttributeSet(AccAttributes* aAttributes) { + if (!aAttributes) { + return nullptr; + } + + AtkAttributeSet* objAttributeSet = nullptr; + + for (auto iter : *aAttributes) { + nsAutoString name; + iter.NameAsString(name); + if (name.Equals(u"placeholder")) { + name.AssignLiteral(u"placeholder-text"); + } + + nsAutoString value; + iter.ValueAsString(value); + + AtkAttribute* objAttr = (AtkAttribute*)g_malloc(sizeof(AtkAttribute)); + objAttr->name = g_strdup(NS_ConvertUTF16toUTF8(name).get()); + objAttr->value = g_strdup(NS_ConvertUTF16toUTF8(value).get()); + objAttributeSet = g_slist_prepend(objAttributeSet, objAttr); + } + + // libspi will free it + return objAttributeSet; +} + +AtkAttributeSet* getAttributesCB(AtkObject* aAtkObj) { + Accessible* acc = GetInternalObj(aAtkObj); + if (!acc) { + return nullptr; + } + RefPtr<AccAttributes> attributes = acc->Attributes(); + return ConvertToAtkAttributeSet(attributes); +} + +const gchar* GetLocaleCB(AtkObject* aAtkObj) { + Accessible* acc = GetInternalObj(aAtkObj); + if (!acc) { + return nullptr; + } + + nsAutoString locale; + acc->Language(locale); + return AccessibleWrap::ReturnString(locale); +} + +AtkObject* getParentCB(AtkObject* aAtkObj) { + if (aAtkObj->accessible_parent) return aAtkObj->accessible_parent; + + Accessible* acc = GetInternalObj(aAtkObj); + if (!acc) { + return nullptr; + } + + Accessible* parent = acc->Parent(); + AtkObject* atkParent = parent ? GetWrapperFor(parent) : nullptr; + if (atkParent) atk_object_set_parent(aAtkObj, atkParent); + + return aAtkObj->accessible_parent; +} + +gint getChildCountCB(AtkObject* aAtkObj) { + Accessible* acc = GetInternalObj(aAtkObj); + if (!acc || nsAccUtils::MustPrune(acc)) { + return 0; + } + return static_cast<gint>(acc->EmbeddedChildCount()); +} + +AtkObject* refChildCB(AtkObject* aAtkObj, gint aChildIndex) { + // aChildIndex should not be less than zero + if (aChildIndex < 0) { + return nullptr; + } + + Accessible* acc = GetInternalObj(aAtkObj); + if (!acc || nsAccUtils::MustPrune(acc)) { + return nullptr; + } + Accessible* accChild = acc->EmbeddedChildAt(aChildIndex); + if (!accChild) { + return nullptr; + } + + AtkObject* childAtkObj = GetWrapperFor(accChild); + NS_ASSERTION(childAtkObj, "Fail to get AtkObj"); + if (!childAtkObj) { + return nullptr; + } + + g_object_ref(childAtkObj); + + if (aAtkObj != childAtkObj->accessible_parent) { + atk_object_set_parent(childAtkObj, aAtkObj); + } + + return childAtkObj; +} + +gint getIndexInParentCB(AtkObject* aAtkObj) { + // We don't use LocalAccessible::IndexInParent() because we don't include text + // leaf nodes as children in ATK. + Accessible* acc = GetInternalObj(aAtkObj); + if (!acc) { + return -1; + } + if (acc->IsDoc()) { + return 0; + } + Accessible* parent = acc->Parent(); + if (!parent) { + return -1; + } + return parent->IndexOfEmbeddedChild(acc); +} + +static void TranslateStates(uint64_t aState, roles::Role aRole, + AtkStateSet* aStateSet) { + // atk doesn't have a read only state so read only things shouldn't be + // editable. However, we don't do this for list items because Gecko always + // exposes those as read only. + if ((aState & states::READONLY) && aRole != roles::LISTITEM) { + aState &= ~states::EDITABLE; + } + + // Convert every state to an entry in AtkStateMap + uint64_t bitMask = 1; + for (auto stateIndex = 0U; stateIndex < gAtkStateMapLen; stateIndex++) { + if (gAtkStateMap[stateIndex] + .atkState) { // There's potentially an ATK state for this + bool isStateOn = (aState & bitMask) != 0; + if (gAtkStateMap[stateIndex].stateMapEntryType == kMapOpposite) { + isStateOn = !isStateOn; + } + if (isStateOn) { + atk_state_set_add_state(aStateSet, gAtkStateMap[stateIndex].atkState); + } + } + bitMask <<= 1; + } +} + +AtkStateSet* refStateSetCB(AtkObject* aAtkObj) { + AtkStateSet* state_set = nullptr; + state_set = ATK_OBJECT_CLASS(parent_class)->ref_state_set(aAtkObj); + + if (Accessible* acc = GetInternalObj(aAtkObj)) { + TranslateStates(acc->State(), acc->Role(), state_set); + } else { + TranslateStates(states::DEFUNCT, roles::NOTHING, state_set); + } + + return state_set; +} + +static void UpdateAtkRelation(RelationType aType, Accessible* aAcc, + AtkRelationType aAtkType, + AtkRelationSet* aAtkSet) { + if (aAtkType == ATK_RELATION_NULL) return; + + AtkRelation* atkRelation = + atk_relation_set_get_relation_by_type(aAtkSet, aAtkType); + if (atkRelation) atk_relation_set_remove(aAtkSet, atkRelation); + + Relation rel(aAcc->RelationByType(aType)); + nsTArray<AtkObject*> targets; + Accessible* tempAcc = nullptr; + while ((tempAcc = rel.Next())) { + targets.AppendElement(GetWrapperFor(tempAcc)); + } + + if (targets.Length()) { + atkRelation = + atk_relation_new(targets.Elements(), targets.Length(), aAtkType); + atk_relation_set_add(aAtkSet, atkRelation); + g_object_unref(atkRelation); + } +} + +AtkRelationSet* refRelationSetCB(AtkObject* aAtkObj) { + AtkRelationSet* relation_set = + ATK_OBJECT_CLASS(parent_class)->ref_relation_set(aAtkObj); + + Accessible* acc = GetInternalObj(aAtkObj); + if (!acc) { + return relation_set; + } + +#define RELATIONTYPE(geckoType, geckoTypeName, atkType, msaaType, ia2Type) \ + UpdateAtkRelation(RelationType::geckoType, acc, atkType, relation_set); + +#include "RelationTypeMap.h" + +#undef RELATIONTYPE + + return relation_set; +} + +// Check if aAtkObj is a valid MaiAtkObject, and return the AccessibleWrap +// for it. +AccessibleWrap* GetAccessibleWrap(AtkObject* aAtkObj) { + NS_ENSURE_TRUE(IS_MAI_OBJECT(aAtkObj), nullptr); + + // If we're working with an ATK object, we need to convert the Accessible + // back to an AccessibleWrap: + Accessible* storedAcc = MAI_ATK_OBJECT(aAtkObj)->acc; + if (!storedAcc) { + return nullptr; + } + auto* accWrap = static_cast<AccessibleWrap*>(storedAcc->AsLocal()); + + // Check if the accessible was deconstructed. + if (!accWrap) return nullptr; + + NS_ENSURE_TRUE(accWrap->GetAtkObject() == aAtkObj, nullptr); + + AccessibleWrap* appAccWrap = ApplicationAcc(); + if (appAccWrap != accWrap && !accWrap->IsValidObject()) { + return nullptr; + } + + return accWrap; +} + +RemoteAccessible* GetProxy(AtkObject* aObj) { + Accessible* acc = GetInternalObj(aObj); + if (!acc) { + return nullptr; + } + + return acc->AsRemote(); +} + +Accessible* GetInternalObj(AtkObject* aObj) { + if (!aObj || !IS_MAI_OBJECT(aObj)) return nullptr; + + return MAI_ATK_OBJECT(aObj)->acc; +} + +AtkObject* GetWrapperFor(Accessible* aAcc) { + if (!aAcc) { + return nullptr; + } + + if (aAcc->IsRemote()) { + return reinterpret_cast<AtkObject*>(aAcc->AsRemote()->GetWrapper()); + } + + return AccessibleWrap::GetAtkObject(aAcc->AsLocal()); +} + +static uint16_t GetInterfacesForProxy(RemoteAccessible* aProxy) { + uint16_t interfaces = 1 << MAI_INTERFACE_COMPONENT; + if (aProxy->IsHyperText()) { + interfaces |= (1 << MAI_INTERFACE_HYPERTEXT) | (1 << MAI_INTERFACE_TEXT) | + (1 << MAI_INTERFACE_EDITABLE_TEXT); + } + + if (aProxy->IsLink()) { + interfaces |= 1 << MAI_INTERFACE_HYPERLINK_IMPL; + } + + if (aProxy->HasNumericValue()) { + interfaces |= 1 << MAI_INTERFACE_VALUE; + } + + if (aProxy->IsTable()) { + interfaces |= 1 << MAI_INTERFACE_TABLE; + } + + if (aProxy->IsTableCell()) { + interfaces |= 1 << MAI_INTERFACE_TABLE_CELL; + } + + if (aProxy->IsImage()) { + interfaces |= 1 << MAI_INTERFACE_IMAGE; + } + + if (aProxy->IsDoc()) { + interfaces |= 1 << MAI_INTERFACE_DOCUMENT; + } + + if (aProxy->IsSelect()) { + interfaces |= 1 << MAI_INTERFACE_SELECTION; + } + + if (aProxy->IsActionable()) { + interfaces |= 1 << MAI_INTERFACE_ACTION; + } + + return interfaces; +} + +void a11y::ProxyCreated(RemoteAccessible* aProxy) { + MOZ_ASSERT(aProxy->RemoteParent() || aProxy->IsDoc(), + "Need parent to check for HyperLink interface"); + GType type = GetMaiAtkType(GetInterfacesForProxy(aProxy)); + NS_ASSERTION(type, "why don't we have a type!"); + + AtkObject* obj = reinterpret_cast<AtkObject*>(g_object_new(type, nullptr)); + if (!obj) return; + + atk_object_initialize(obj, static_cast<Accessible*>(aProxy)); + obj->role = ATK_ROLE_INVALID; + obj->layer = ATK_LAYER_INVALID; + aProxy->SetWrapper(reinterpret_cast<uintptr_t>(obj)); +} + +void a11y::ProxyDestroyed(RemoteAccessible* aProxy) { + auto obj = reinterpret_cast<MaiAtkObject*>(aProxy->GetWrapper()); + if (!obj) { + return; + } + + obj->Shutdown(); + g_object_unref(obj); + aProxy->SetWrapper(0); +} + +void a11y::PlatformEvent(Accessible* aTarget, uint32_t aEventType) { + AtkObject* wrapper = GetWrapperFor(aTarget); + + switch (aEventType) { + case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE: + if (aTarget->IsDoc()) { + g_signal_emit_by_name(wrapper, "load_complete"); + } + // XXX - Handle native dialog accessibles. + if (!aTarget->IsRoot() && aTarget->HasARIARole() && + aTarget->Role() == roles::DIALOG) { + guint id = g_signal_lookup("activate", MAI_TYPE_ATK_OBJECT); + g_signal_emit(wrapper, id, 0); + } + break; + case nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD: + if (aTarget->IsDoc()) { + g_signal_emit_by_name(wrapper, "reload"); + } + break; + case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED: + if (aTarget->IsDoc()) { + g_signal_emit_by_name(wrapper, "load_stopped"); + } + break; + case nsIAccessibleEvent::EVENT_MENUPOPUP_START: + atk_focus_tracker_notify(wrapper); // fire extra focus event + atk_object_notify_state_change(wrapper, ATK_STATE_VISIBLE, true); + atk_object_notify_state_change(wrapper, ATK_STATE_SHOWING, true); + break; + case nsIAccessibleEvent::EVENT_MENUPOPUP_END: + atk_object_notify_state_change(wrapper, ATK_STATE_VISIBLE, false); + atk_object_notify_state_change(wrapper, ATK_STATE_SHOWING, false); + break; + case nsIAccessibleEvent::EVENT_ALERT: + // A hack using state change showing events as alert events. + atk_object_notify_state_change(wrapper, ATK_STATE_SHOWING, true); + break; + case nsIAccessibleEvent::EVENT_VALUE_CHANGE: + case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE: + if (aTarget->HasNumericValue()) { + // Make sure this is a numeric value. Don't fire for string value + // changes (e.g. text editing) ATK values are always numeric. + g_object_notify((GObject*)wrapper, "accessible-value"); + } + break; + case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: + g_signal_emit_by_name(wrapper, "text_selection_changed"); + break; + case nsIAccessibleEvent::EVENT_SELECTION_WITHIN: + g_signal_emit_by_name(wrapper, "selection_changed"); + break; + case nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED: + g_signal_emit_by_name(wrapper, "text-attributes-changed"); + break; + case nsIAccessibleEvent::EVENT_NAME_CHANGE: { + nsAutoString newName; + aTarget->Name(newName); + MaybeFireNameChange(wrapper, newName); + break; + } + case nsIAccessibleEvent::EVENT_WINDOW_ACTIVATE: { + guint id = g_signal_lookup("activate", MAI_TYPE_ATK_OBJECT); + g_signal_emit(wrapper, id, 0); + // Always fire a current focus event after activation. + FocusMgr()->ForceFocusEvent(); + break; + } + case nsIAccessibleEvent::EVENT_WINDOW_DEACTIVATE: { + guint id = g_signal_lookup("deactivate", MAI_TYPE_ATK_OBJECT); + g_signal_emit(wrapper, id, 0); + break; + } + case nsIAccessibleEvent::EVENT_WINDOW_MAXIMIZE: { + guint id = g_signal_lookup("maximize", MAI_TYPE_ATK_OBJECT); + g_signal_emit(wrapper, id, 0); + break; + } + case nsIAccessibleEvent::EVENT_WINDOW_MINIMIZE: { + guint id = g_signal_lookup("minimize", MAI_TYPE_ATK_OBJECT); + g_signal_emit(wrapper, id, 0); + break; + } + case nsIAccessibleEvent::EVENT_WINDOW_RESTORE: { + guint id = g_signal_lookup("restore", MAI_TYPE_ATK_OBJECT); + g_signal_emit(wrapper, id, 0); + break; + } + } +} + +void a11y::PlatformStateChangeEvent(Accessible* aTarget, uint64_t aState, + bool aEnabled) { + MaiAtkObject* atkObj = MAI_ATK_OBJECT(GetWrapperFor(aTarget)); + atkObj->FireStateChangeEvent(aState, aEnabled); +} + +void a11y::PlatformFocusEvent(Accessible* aTarget, + const LayoutDeviceIntRect& aCaretRect) { + AtkObject* wrapper = GetWrapperFor(aTarget); + + // XXX Do we really need this check? If so, do we need a similar check for + // RemoteAccessible? + if (LocalAccessible* localTarget = aTarget->AsLocal()) { + a11y::RootAccessible* rootAcc = localTarget->RootAccessible(); + if (!rootAcc || !rootAcc->IsActivated()) { + return; + } + } + + atk_focus_tracker_notify(wrapper); + atk_object_notify_state_change(wrapper, ATK_STATE_FOCUSED, true); +} + +void a11y::PlatformCaretMoveEvent(Accessible* aTarget, int32_t aOffset, + bool aIsSelectionCollapsed, + int32_t aGranularity, + const LayoutDeviceIntRect& aCaretRect, + bool aFromUser) { + AtkObject* wrapper = GetWrapperFor(aTarget); + g_signal_emit_by_name(wrapper, "text_caret_moved", aOffset); +} + +void MaiAtkObject::FireStateChangeEvent(uint64_t aState, bool aEnabled) { + auto state = aState; + int32_t stateIndex = -1; + while (state > 0) { + ++stateIndex; + state >>= 1; + } + + MOZ_ASSERT( + stateIndex >= 0 && stateIndex < static_cast<int32_t>(gAtkStateMapLen), + "No ATK state for internal state was found"); + if (stateIndex < 0 || stateIndex >= static_cast<int32_t>(gAtkStateMapLen)) { + return; + } + + if (gAtkStateMap[stateIndex].atkState != kNone) { + MOZ_ASSERT(gAtkStateMap[stateIndex].stateMapEntryType != kNoStateChange, + "State changes should not fired for this state"); + + if (gAtkStateMap[stateIndex].stateMapEntryType == kMapOpposite) { + aEnabled = !aEnabled; + } + + // Fire state change for first state if there is one to map + atk_object_notify_state_change(&parent, gAtkStateMap[stateIndex].atkState, + aEnabled); + } +} + +void a11y::PlatformTextChangeEvent(Accessible* aTarget, const nsAString& aStr, + int32_t aStart, uint32_t aLen, + bool aIsInsert, bool aFromUser) { + MaiAtkObject* atkObj = MAI_ATK_OBJECT(GetWrapperFor(aTarget)); + atkObj->FireTextChangeEvent(aStr, aStart, aLen, aIsInsert, aFromUser); +} + +#define OLD_TEXT_INSERTED "text_changed::insert" +#define OLD_TEXT_REMOVED "text_changed::delete" +static const char* oldTextChangeStrings[2][2] = { + {OLD_TEXT_REMOVED NON_USER_EVENT, OLD_TEXT_INSERTED NON_USER_EVENT}, + {OLD_TEXT_REMOVED, OLD_TEXT_INSERTED}}; + +#define TEXT_INSERTED "text-insert" +#define TEXT_REMOVED "text-remove" +#define NON_USER_DETAIL "::system" +static const char* textChangedStrings[2][2] = { + {TEXT_REMOVED NON_USER_DETAIL, TEXT_INSERTED NON_USER_DETAIL}, + {TEXT_REMOVED, TEXT_INSERTED}}; + +void MaiAtkObject::FireTextChangeEvent(const nsAString& aStr, int32_t aStart, + uint32_t aLen, bool aIsInsert, + bool aFromUser) { + if (gAvailableAtkSignals == eUnknown) { + gAvailableAtkSignals = g_signal_lookup("text-insert", G_OBJECT_TYPE(this)) + ? eHaveNewAtkTextSignals + : eNoNewAtkSignals; + } + + if (gAvailableAtkSignals == eNoNewAtkSignals) { + // XXX remove this code and the gHaveNewTextSignals check when we can + // stop supporting old atk since it doesn't really work anyway + // see bug 619002 + const char* signal_name = oldTextChangeStrings[aFromUser][aIsInsert]; + g_signal_emit_by_name(this, signal_name, aStart, aLen); + } else { + const char* signal_name = textChangedStrings[aFromUser][aIsInsert]; + g_signal_emit_by_name(this, signal_name, aStart, aLen, + NS_ConvertUTF16toUTF8(aStr).get()); + } +} + +void a11y::PlatformShowHideEvent(Accessible* aTarget, Accessible* aParent, + bool aInsert, bool aFromUser) { + AtkObject* atkObj = GetWrapperFor(aTarget); + if (!aInsert) { + // XXX - Handle native dialog accessibles. + if (!aTarget->IsRoot() && aTarget->HasARIARole() && + aTarget->Role() == roles::DIALOG) { + guint id = g_signal_lookup("deactivate", MAI_TYPE_ATK_OBJECT); + g_signal_emit(atkObj, id, 0); + } + } + + MaiAtkObject* obj = MAI_ATK_OBJECT(atkObj); + obj->FireAtkShowHideEvent(GetWrapperFor(aParent), aInsert, aFromUser); +} + +#define ADD_EVENT "children_changed::add" +#define HIDE_EVENT "children_changed::remove" + +static const char* kMutationStrings[2][2] = { + {HIDE_EVENT NON_USER_EVENT, ADD_EVENT NON_USER_EVENT}, + {HIDE_EVENT, ADD_EVENT}, +}; + +void MaiAtkObject::FireAtkShowHideEvent(AtkObject* aParent, bool aIsAdded, + bool aFromUser) { + if (!aParent) { + // XXX ATK needs a parent for these events. However, we might have already + // unbound from the parent by the time we fire a hide event. Ideally, we + // need to find a way to keep the parent around, but ATK clients don't seem + // to care about these missing events. + MOZ_ASSERT(!aIsAdded); + return; + } + int32_t indexInParent = getIndexInParentCB(&this->parent); + const char* signal_name = kMutationStrings[aFromUser][aIsAdded]; + g_signal_emit_by_name(aParent, signal_name, indexInParent, this, nullptr); +} + +void a11y::PlatformSelectionEvent(Accessible*, Accessible* aWidget, uint32_t) { + MaiAtkObject* obj = MAI_ATK_OBJECT(GetWrapperFor(aWidget)); + g_signal_emit_by_name(obj, "selection_changed"); +} + +// static +void AccessibleWrap::GetKeyBinding(Accessible* aAccessible, + nsAString& aResult) { + // Return all key bindings including access key and keyboard shortcut. + + // Get access key. + nsAutoString keyBindingsStr; + KeyBinding keyBinding = aAccessible->AccessKey(); + if (!keyBinding.IsEmpty()) { + keyBinding.AppendToString(keyBindingsStr, KeyBinding::eAtkFormat); + + Accessible* parent = aAccessible->Parent(); + roles::Role role = parent ? parent->Role() : roles::NOTHING; + if (role == roles::PARENT_MENUITEM || role == roles::MENUITEM || + role == roles::RADIO_MENU_ITEM || role == roles::CHECK_MENU_ITEM) { + // It is submenu, expose keyboard shortcuts from menu hierarchy like + // "s;<Alt>f:s" + nsAutoString keysInHierarchyStr = keyBindingsStr; + do { + KeyBinding parentKeyBinding = parent->AccessKey(); + if (!parentKeyBinding.IsEmpty()) { + nsAutoString str; + parentKeyBinding.ToString(str, KeyBinding::eAtkFormat); + str.Append(':'); + + keysInHierarchyStr.Insert(str, 0); + } + } while ((parent = parent->Parent()) && parent->Role() != roles::MENUBAR); + + keyBindingsStr.Append(';'); + keyBindingsStr.Append(keysInHierarchyStr); + } + } else { + // No access key, add ';' to point this. + keyBindingsStr.Append(';'); + } + + // Get keyboard shortcut. + keyBindingsStr.Append(';'); + if (LocalAccessible* localAcc = aAccessible->AsLocal()) { + keyBinding = localAcc->KeyboardShortcut(); + if (!keyBinding.IsEmpty()) { + keyBinding.AppendToString(keyBindingsStr, KeyBinding::eAtkFormat); + } + } + aResult = keyBindingsStr; +} + +// static +Accessible* AccessibleWrap::GetColumnHeader(TableAccessible* aAccessible, + int32_t aColIdx) { + if (!aAccessible) { + return nullptr; + } + + Accessible* cell = aAccessible->CellAt(0, aColIdx); + if (!cell) { + return nullptr; + } + + // If the cell at the first row is column header then assume it is column + // header for all rows, + if (cell->Role() == roles::COLUMNHEADER) { + return cell; + } + + // otherwise get column header for the data cell at the first row. + TableCellAccessible* tableCell = cell->AsTableCell(); + if (!tableCell) { + return nullptr; + } + + AutoTArray<Accessible*, 10> headerCells; + tableCell->ColHeaderCells(&headerCells); + if (headerCells.IsEmpty()) { + return nullptr; + } + + return headerCells[0]; +} + +// static +Accessible* AccessibleWrap::GetRowHeader(TableAccessible* aAccessible, + int32_t aRowIdx) { + if (!aAccessible) { + return nullptr; + } + + Accessible* cell = aAccessible->CellAt(aRowIdx, 0); + if (!cell) { + return nullptr; + } + + // If the cell at the first column is row header then assume it is row + // header for all columns, + if (cell->Role() == roles::ROWHEADER) { + return cell; + } + + // otherwise get row header for the data cell at the first column. + TableCellAccessible* tableCell = cell->AsTableCell(); + if (!tableCell) { + return nullptr; + } + + AutoTArray<Accessible*, 10> headerCells; + tableCell->RowHeaderCells(&headerCells); + if (headerCells.IsEmpty()) { + return nullptr; + } + + return headerCells[0]; +} diff --git a/accessible/atk/AccessibleWrap.h b/accessible/atk/AccessibleWrap.h new file mode 100644 index 0000000000..e12189405d --- /dev/null +++ b/accessible/atk/AccessibleWrap.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __NS_ACCESSIBLE_WRAP_H__ +#define __NS_ACCESSIBLE_WRAP_H__ + +#include "nsCOMPtr.h" +#include "LocalAccessible.h" + +struct _AtkObject; +typedef struct _AtkObject AtkObject; + +enum AtkProperty { + PROP_0, // gobject convention + PROP_NAME, + PROP_DESCRIPTION, + PROP_PARENT, // ancestry has changed + PROP_ROLE, + PROP_LAYER, + PROP_MDI_ZORDER, + PROP_TABLE_CAPTION, + PROP_TABLE_COLUMN_DESCRIPTION, + PROP_TABLE_COLUMN_HEADER, + PROP_TABLE_ROW_DESCRIPTION, + PROP_TABLE_ROW_HEADER, + PROP_TABLE_SUMMARY, + PROP_LAST // gobject convention +}; + +struct AtkPropertyChange { + int32_t type; // property type as listed above + void* oldvalue; + void* newvalue; +}; + +namespace mozilla { +namespace a11y { + +class MaiHyperlink; + +/** + * Atk specific functionality for an accessibility tree node that originated in + * mDoc's content process. + * + * AccessibleWrap, and its descendents in atk directory provide the + * implementation of AtkObject. + */ +class AccessibleWrap : public LocalAccessible { + public: + AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc); + virtual ~AccessibleWrap(); + void ShutdownAtkObject(); + + virtual void Shutdown() override; + + // return the atk object for this AccessibleWrap + virtual void GetNativeInterface(void** aOutAccessible) override; + + AtkObject* GetAtkObject(void); + static AtkObject* GetAtkObject(LocalAccessible* aAccessible); + + bool IsValidObject(); + + static const char* ReturnString(nsAString& aString) { + static nsCString returnedString; + CopyUTF16toUTF8(aString, returnedString); + return returnedString.get(); + } + + static void GetKeyBinding(Accessible* aAccessible, nsAString& aResult); + + static Accessible* GetColumnHeader(TableAccessible* aAccessible, + int32_t aColIdx); + static Accessible* GetRowHeader(TableAccessible* aAccessible, + int32_t aRowIdx); + + protected: + nsresult FireAtkStateChangeEvent(AccEvent* aEvent, AtkObject* aObject); + nsresult FireAtkTextChangedEvent(AccEvent* aEvent, AtkObject* aObject); + + AtkObject* mAtkObject; + + private: + uint16_t CreateMaiInterfaces(); +}; + +} // namespace a11y +} // namespace mozilla + +#endif /* __NS_ACCESSIBLE_WRAP_H__ */ diff --git a/accessible/atk/ApplicationAccessibleWrap.cpp b/accessible/atk/ApplicationAccessibleWrap.cpp new file mode 100644 index 0000000000..4c3e615387 --- /dev/null +++ b/accessible/atk/ApplicationAccessibleWrap.cpp @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ApplicationAccessibleWrap.h" + +#include "nsMai.h" +#include "nsAccessibilityService.h" + +#include <gtk/gtk.h> +#include "atk/atkobject.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +// ApplicationAccessibleWrap + +ApplicationAccessibleWrap::ApplicationAccessibleWrap() = default; + +ApplicationAccessibleWrap::~ApplicationAccessibleWrap() { + AccessibleWrap::ShutdownAtkObject(); +} + +gboolean toplevel_event_watcher(GSignalInvocationHint* ihint, + guint n_param_values, + const GValue* param_values, gpointer data) { + static GQuark sQuark_gecko_acc_obj = 0; + + if (!sQuark_gecko_acc_obj) { + sQuark_gecko_acc_obj = g_quark_from_static_string("GeckoAccObj"); + } + + if (nsAccessibilityService::IsShutdown()) return TRUE; + + GObject* object = + reinterpret_cast<GObject*>(g_value_get_object(param_values)); + if (!GTK_IS_WINDOW(object)) return TRUE; + + AtkObject* child = gtk_widget_get_accessible(GTK_WIDGET(object)); + AtkRole role = atk_object_get_role(child); + + // GTK native dialog + if (!IS_MAI_OBJECT(child) && + (role == ATK_ROLE_DIALOG || role == ATK_ROLE_FILE_CHOOSER || + role == ATK_ROLE_COLOR_CHOOSER || role == ATK_ROLE_FONT_CHOOSER)) { + if (data == reinterpret_cast<gpointer>(nsIAccessibleEvent::EVENT_SHOW)) { + // Attach the dialog accessible to app accessible tree + LocalAccessible* windowAcc = + GetAccService()->AddNativeRootAccessible(child); + g_object_set_qdata(G_OBJECT(child), sQuark_gecko_acc_obj, + reinterpret_cast<gpointer>(windowAcc)); + + } else { + // Deattach the dialog accessible + LocalAccessible* windowAcc = reinterpret_cast<LocalAccessible*>( + g_object_get_qdata(G_OBJECT(child), sQuark_gecko_acc_obj)); + if (windowAcc) { + GetAccService()->RemoveNativeRootAccessible(windowAcc); + g_object_set_qdata(G_OBJECT(child), sQuark_gecko_acc_obj, nullptr); + } + } + } + + return TRUE; +} + +ENameValueFlag ApplicationAccessibleWrap::Name(nsString& aName) const { + // ATK doesn't provide a way to obtain an application name (for example, + // Firefox or Thunderbird) like IA2 does. Thus let's return an application + // name as accessible name that was used to get a branding name (for example, + // Minefield aka nightly Firefox or Daily aka nightly Thunderbird). + AppName(aName); + return eNameOK; +} + +void ApplicationAccessibleWrap::GetNativeInterface(void** aOutAccessible) { + *aOutAccessible = nullptr; + + if (!mAtkObject) { + mAtkObject = reinterpret_cast<AtkObject*>( + g_object_new(MAI_TYPE_ATK_OBJECT, nullptr)); + if (!mAtkObject) return; + + atk_object_initialize(mAtkObject, static_cast<Accessible*>(this)); + mAtkObject->role = ATK_ROLE_INVALID; + mAtkObject->layer = ATK_LAYER_INVALID; + } + + *aOutAccessible = mAtkObject; +} + +struct AtkRootAccessibleAddedEvent { + AtkObject* app_accessible; + AtkObject* root_accessible; + uint32_t index; +}; + +gboolean fireRootAccessibleAddedCB(gpointer data) { + AtkRootAccessibleAddedEvent* eventData = (AtkRootAccessibleAddedEvent*)data; + g_signal_emit_by_name(eventData->app_accessible, "children_changed::add", + eventData->index, eventData->root_accessible, nullptr); + g_object_unref(eventData->app_accessible); + g_object_unref(eventData->root_accessible); + free(data); + + return FALSE; +} + +bool ApplicationAccessibleWrap::InsertChildAt(uint32_t aIdx, + LocalAccessible* aChild) { + if (!ApplicationAccessible::InsertChildAt(aIdx, aChild)) return false; + + AtkObject* atkAccessible = AccessibleWrap::GetAtkObject(aChild); + atk_object_set_parent(atkAccessible, mAtkObject); + + uint32_t count = mChildren.Length(); + + // Emit children_changed::add in a timeout + // to make sure aRootAccWrap is fully initialized. + AtkRootAccessibleAddedEvent* eventData = + (AtkRootAccessibleAddedEvent*)malloc(sizeof(AtkRootAccessibleAddedEvent)); + if (eventData) { + eventData->app_accessible = mAtkObject; + eventData->root_accessible = atkAccessible; + eventData->index = count - 1; + g_object_ref(mAtkObject); + g_object_ref(atkAccessible); + g_timeout_add(0, fireRootAccessibleAddedCB, eventData); + } + + return true; +} + +bool ApplicationAccessibleWrap::RemoveChild(LocalAccessible* aChild) { + int32_t index = aChild->IndexInParent(); + + AtkObject* atkAccessible = AccessibleWrap::GetAtkObject(aChild); + atk_object_set_parent(atkAccessible, nullptr); + g_signal_emit_by_name(mAtkObject, "children_changed::remove", index, + atkAccessible, nullptr); + + return ApplicationAccessible::RemoveChild(aChild); +} diff --git a/accessible/atk/ApplicationAccessibleWrap.h b/accessible/atk/ApplicationAccessibleWrap.h new file mode 100644 index 0000000000..fe14dc045a --- /dev/null +++ b/accessible/atk/ApplicationAccessibleWrap.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_a11y_ApplicationAccessibleWrap_h__ +#define mozilla_a11y_ApplicationAccessibleWrap_h__ + +#include "ApplicationAccessible.h" + +namespace mozilla { +namespace a11y { + +class ApplicationAccessibleWrap : public ApplicationAccessible { + public: + ApplicationAccessibleWrap(); + virtual ~ApplicationAccessibleWrap(); + + // LocalAccessible + virtual mozilla::a11y::ENameValueFlag Name(nsString& aName) const override; + virtual bool InsertChildAt(uint32_t aIdx, LocalAccessible* aChild) override; + virtual bool RemoveChild(LocalAccessible* aChild) override; + + /** + * Return the atk object for app root accessible. + */ + virtual void GetNativeInterface(void** aOutAccessible) override; +}; + +} // namespace a11y +} // namespace mozilla + +#endif /* __NS_APP_ROOT_ACCESSIBLE_H__ */ diff --git a/accessible/atk/DOMtoATK.cpp b/accessible/atk/DOMtoATK.cpp new file mode 100644 index 0000000000..2c23731bba --- /dev/null +++ b/accessible/atk/DOMtoATK.cpp @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMtoATK.h" +#include "nsUTF8Utils.h" + +namespace mozilla { +namespace a11y { + +namespace DOMtoATK { + +void AddBOMs(nsACString& aDest, const nsACString& aSource) { + uint32_t destlength = 0; + + // First compute how much room we will need. + for (uint32_t srci = 0; srci < aSource.Length();) { + int bytes = UTF8traits::bytes(aSource[srci]); + if (bytes >= 4) { + // Non-BMP character, will add a BOM after it. + destlength += 3; + } + // Skip whole character encoding. + srci += bytes; + destlength += bytes; + } + + uint32_t desti = 0; // Index within aDest. + + // Add BOMs after non-BMP characters. + aDest.SetLength(destlength); + for (uint32_t srci = 0; srci < aSource.Length();) { + uint32_t bytes = UTF8traits::bytes(aSource[srci]); + + MOZ_ASSERT(bytes <= aSource.Length() - srci, + "We should have the whole sequence"); + + // Copy whole sequence. + aDest.Replace(desti, bytes, Substring(aSource, srci, bytes)); + desti += bytes; + srci += bytes; + + if (bytes >= 4) { + // More than 4 bytes in UTF-8 encoding exactly means more than 16 encoded + // bits. This is thus a non-BMP character which needed a surrogate + // pair to get encoded in UTF-16, add a BOM after it. + + // And add a BOM after it. + aDest.Replace(desti, 3, "\xEF\xBB\xBF"); + desti += 3; + } + } + MOZ_ASSERT(desti == destlength, + "Incoherency between computed length" + "and actually translated length"); +} + +void ATKStringConverterHelper::AdjustOffsets(gint* aStartOffset, + gint* aEndOffset, gint count) { + MOZ_ASSERT(!mAdjusted, + "DOMtoATK::ATKStringConverterHelper::AdjustOffsets needs to be " + "called only once"); + + if (*aStartOffset > 0) { + (*aStartOffset)--; + mStartShifted = true; + } + + if (*aEndOffset >= 0 && *aEndOffset < count) { + (*aEndOffset)++; + mEndShifted = true; + } + +#ifdef DEBUG + mAdjusted = true; +#endif +} + +gchar* ATKStringConverterHelper::FinishUTF16toUTF8(nsCString& aStr) { + int skip = 0; + + if (mStartShifted) { + // AdjustOffsets added a leading character. + + MOZ_ASSERT(aStr.Length() > 0, "There should be a leading character"); + MOZ_ASSERT( + static_cast<int>(aStr.Length()) >= UTF8traits::bytes(aStr.CharAt(0)), + "The leading character should be complete"); + + // drop first character + skip = UTF8traits::bytes(aStr.CharAt(0)); + } + + if (mEndShifted) { + // AdjustOffsets added a trailing character. + + MOZ_ASSERT(aStr.Length() > 0, "There should be a trailing character"); + + int trail = -1; + // Find beginning of last character. + for (trail = aStr.Length() - 1; trail >= 0; trail--) { + if (!UTF8traits::isInSeq(aStr.CharAt(trail))) { + break; + } + } + MOZ_ASSERT(trail >= 0, + "There should be at least a whole trailing character"); + MOZ_ASSERT(trail + UTF8traits::bytes(aStr.CharAt(trail)) == + static_cast<int>(aStr.Length()), + "The trailing character should be complete"); + + // Drop the last character. + aStr.Truncate(trail); + } + + // copy and return, libspi will free it + return g_strdup(aStr.get() + skip); +} + +gchar* ATKStringConverterHelper::ConvertAdjusted(const nsAString& aStr) { + MOZ_ASSERT(mAdjusted, + "DOMtoATK::ATKStringConverterHelper::AdjustOffsets needs to be " + "called before ATKStringConverterHelper::ConvertAdjusted"); + + NS_ConvertUTF16toUTF8 cautoStr(aStr); + if (!cautoStr.get()) { + return nullptr; + } + + nsAutoCString cautoStrBOMs; + AddBOMs(cautoStrBOMs, cautoStr); + return FinishUTF16toUTF8(cautoStrBOMs); +} + +gchar* Convert(const nsAString& aStr) { + NS_ConvertUTF16toUTF8 cautoStr(aStr); + if (!cautoStr.get()) { + return nullptr; + } + + nsAutoCString cautoStrBOMs; + AddBOMs(cautoStrBOMs, cautoStr); + return g_strdup(cautoStrBOMs.get()); +} + +} // namespace DOMtoATK + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/atk/DOMtoATK.h b/accessible/atk/DOMtoATK.h new file mode 100644 index 0000000000..322358bc6e --- /dev/null +++ b/accessible/atk/DOMtoATK.h @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <glib.h> +#include <cstdint> +#include "mozilla/a11y/HyperTextAccessibleBase.h" +#include "nsCharTraits.h" +#include "nsString.h" + +/** + * ATK offsets are counted in unicode codepoints, while DOM offsets are counted + * in UTF-16 code units. That makes a difference for non-BMP characters, + * which need two UTF-16 code units to be represented (a pair of surrogates), + * while they are just one unicode character. + * + * To keep synchronization between ATK offsets (unicode codepoints) and DOM + * offsets (UTF-16 code units), after translation from UTF-16 to UTF-8 we add a + * BOM after each non-BMP character (which would otherwise use 2 UTF-16 + * code units for only 1 unicode codepoint). + * + * BOMs (Byte Order Marks, U+FEFF, also known as ZERO WIDTH NO-BREAK SPACE, but + * that usage is deprecated) normally only appear at the beginning of unicode + * files, but their occurrence within text (notably after cut&paste) is not + * uncommon, and are thus considered as non-text. + * + * Since the selection requested through ATK may not contain both surrogates + * at the ends of the selection, we need to fetch one UTF-16 code point more + * on both side, and get rid of it before returning the string to ATK. The + * ATKStringConverterHelper class maintains this, NewATKString should be used + * to call it properly. + * + * In the end, + * - if the start is between the high and low surrogates, the UTF-8 result + * includes a BOM from it but not the character + * - if the end is between the high and low surrogates, the UTF-8 result + * includes the character but *not* the BOM + * - all non-BMP characters that are fully in the string are in the UTF-8 result + * as character followed by BOM + */ +namespace mozilla { +namespace a11y { + +namespace DOMtoATK { + +/** + * Converts a string of accessible text into ATK gchar* string (by adding + * BOMs). This can be used when offsets do not need to be adjusted because + * ends of the string can not fall between surrogates. + */ +gchar* Convert(const nsAString& aStr); + +/** + * Add a BOM after each non-BMP character. + */ +void AddBOMs(nsACString& aDest, const nsACString& aSource); + +class ATKStringConverterHelper { + public: + ATKStringConverterHelper(void) + : +#ifdef DEBUG + mAdjusted(false), +#endif + mStartShifted(false), + mEndShifted(false) { + } + + /** + * In order to properly get non-BMP values, offsets need to be changed + * to get one character more on each end, so that ConvertUTF16toUTF8 can + * convert surrogates even if the originally requested offsets fall between + * them. + */ + void AdjustOffsets(gint* aStartOffset, gint* aEndOffset, gint count); + + /** + * Converts a string of accessible text with adjusted offsets into ATK + * gchar* string (by adding BOMs). Note, AdjustOffsets has to be called + * before getting the text passed to this. + */ + gchar* ConvertAdjusted(const nsAString& aStr); + + private: + /** + * Remove the additional characters requested by PrepareUTF16toUTF8. + */ + gchar* FinishUTF16toUTF8(nsCString& aStr); + +#ifdef DEBUG + bool mAdjusted; +#endif + bool mStartShifted; + bool mEndShifted; +}; + +/** + * Get text from aAccessible, using ATKStringConverterHelper to properly + * introduce appropriate BOMs. + */ +inline gchar* NewATKString(HyperTextAccessibleBase* aAccessible, + gint aStartOffset, gint aEndOffset) { + gint startOffset = aStartOffset, endOffset = aEndOffset; + ATKStringConverterHelper converter; + converter.AdjustOffsets(&startOffset, &endOffset, + gint(aAccessible->CharacterCount())); + nsAutoString str; + aAccessible->TextSubstring(startOffset, endOffset, str); + + if (str.Length() == 0) { + // Bogus offsets, or empty string, either way we do not need conversion. + return g_strdup(""); + } + + return converter.ConvertAdjusted(str); +} + +/** + * Get a character from aAccessible, fetching more data as appropriate to + * properly get non-BMP characters or a BOM as appropriate. + */ +inline gunichar ATKCharacter(HyperTextAccessibleBase* aAccessible, + gint aOffset) { + // char16_t is unsigned short in Mozilla, gnuichar is guint32 in glib. + gunichar character = static_cast<gunichar>(aAccessible->CharAt(aOffset)); + + if (NS_IS_LOW_SURROGATE(character)) { + // Trailing surrogate, return BOM instead. + return 0xFEFF; + } + + if (NS_IS_HIGH_SURROGATE(character)) { + // Heading surrogate, get the trailing surrogate and combine them. + gunichar characterLow = + static_cast<gunichar>(aAccessible->CharAt(aOffset + 1)); + + if (!NS_IS_LOW_SURROGATE(characterLow)) { + // It should have been a trailing surrogate... Flag the error. + return 0xFFFD; + } + return SURROGATE_TO_UCS4(character, characterLow); + } + + return character; +} + +} // namespace DOMtoATK + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/atk/DocAccessibleWrap.cpp b/accessible/atk/DocAccessibleWrap.cpp new file mode 100644 index 0000000000..f3dfba71ac --- /dev/null +++ b/accessible/atk/DocAccessibleWrap.cpp @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DocAccessibleWrap.h" +#include "mozilla/PresShell.h" +#include "nsIWidgetListener.h" +#include "nsTArray.h" +#include "nsWindow.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// DocAccessibleWrap +//////////////////////////////////////////////////////////////////////////////// + +DocAccessibleWrap::DocAccessibleWrap(dom::Document* aDocument, + PresShell* aPresShell) + : DocAccessible(aDocument, aPresShell) {} + +DocAccessibleWrap::~DocAccessibleWrap() {} + +bool DocAccessibleWrap::IsActivated() { + if (nsWindow* window = nsWindow::GetFocusedWindow()) { + if (nsIWidgetListener* listener = window->GetWidgetListener()) { + if (PresShell* presShell = listener->GetPresShell()) { + return presShell == PresShellPtr(); + } + } + } + + return false; +} diff --git a/accessible/atk/DocAccessibleWrap.h b/accessible/atk/DocAccessibleWrap.h new file mode 100644 index 0000000000..883a4b8f0a --- /dev/null +++ b/accessible/atk/DocAccessibleWrap.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* For documentation of the accessibility architecture, + * see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html + */ + +#ifndef mozilla_a11y_DocAccessibleWrap_h__ +#define mozilla_a11y_DocAccessibleWrap_h__ + +#include "DocAccessible.h" + +namespace mozilla { + +class PresShell; + +namespace a11y { + +class DocAccessibleWrap : public DocAccessible { + public: + DocAccessibleWrap(dom::Document* aDocument, PresShell* aPresShell); + virtual ~DocAccessibleWrap(); + + bool IsActivated(); +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/atk/InterfaceInitFuncs.h b/accessible/atk/InterfaceInitFuncs.h new file mode 100644 index 0000000000..43ed8ff4ee --- /dev/null +++ b/accessible/atk/InterfaceInitFuncs.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ATK_INTERFACE_INIT_FUNCS_H_ +#define ATK_INTERFACE_INIT_FUNCS_H_ + +#include <atk/atk.h> + +namespace mozilla { +namespace a11y { + +class AccessibleWrap; + +} // namespace a11y +} // namespace mozilla + +extern "C" { +void actionInterfaceInitCB(AtkActionIface* aIface); +void componentInterfaceInitCB(AtkComponentIface* aIface); +void documentInterfaceInitCB(AtkDocumentIface* aIface); +void editableTextInterfaceInitCB(AtkEditableTextIface* aIface); +void hyperlinkImplInterfaceInitCB(AtkHyperlinkImplIface* aIface); +void hypertextInterfaceInitCB(AtkHypertextIface* aIface); +void imageInterfaceInitCB(AtkImageIface* aIface); +void selectionInterfaceInitCB(AtkSelectionIface* aIface); +void tableInterfaceInitCB(AtkTableIface* aIface); +void tableCellInterfaceInitCB(AtkTableCellIface* aIface); +void textInterfaceInitCB(AtkTextIface* aIface); +void valueInterfaceInitCB(AtkValueIface* aIface); +} + +/** + * XXX these should live in a file of utils for atk. + */ +AtkObject* refAccessibleAtPointHelper(AtkObject* aAtkObj, gint aX, gint aY, + AtkCoordType aCoordType); +void getExtentsHelper(AtkObject* aAtkObj, gint* aX, gint* aY, gint* aWidth, + gint* aHeight, AtkCoordType aCoordType); + +#endif // ATK_INTERFACE_INIT_FUNCS_H_ diff --git a/accessible/atk/Platform.cpp b/accessible/atk/Platform.cpp new file mode 100644 index 0000000000..e166fcfc32 --- /dev/null +++ b/accessible/atk/Platform.cpp @@ -0,0 +1,271 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Platform.h" + +#include "nsIAccessibleEvent.h" +#include "nsIGSettingsService.h" +#include "nsMai.h" +#include "nsServiceManagerUtils.h" +#include "prenv.h" +#include "prlink.h" + +#ifdef MOZ_ENABLE_DBUS +# include <dbus/dbus.h> +#endif +#include <gtk/gtk.h> + +using namespace mozilla; +using namespace mozilla::a11y; + +int atkMajorVersion = 1, atkMinorVersion = 12, atkMicroVersion = 0; + +GType (*gAtkTableCellGetTypeFunc)(); + +extern "C" { +typedef GType (*AtkGetTypeType)(void); +typedef void (*AtkBridgeAdaptorInit)(int*, char**[]); +} + +static PRLibrary* sATKLib = nullptr; +static const char sATKLibName[] = "libatk-1.0.so.0"; +static const char sATKHyperlinkImplGetTypeSymbol[] = + "atk_hyperlink_impl_get_type"; + +gboolean toplevel_event_watcher(GSignalInvocationHint*, guint, const GValue*, + gpointer); +static bool sToplevel_event_hook_added = false; +static gulong sToplevel_show_hook = 0; +static gulong sToplevel_hide_hook = 0; + +GType g_atk_hyperlink_impl_type = G_TYPE_INVALID; + +struct AtkBridgeModule { + const char* libName; + PRLibrary* lib; + const char* initName; + AtkBridgeAdaptorInit init; +}; + +static AtkBridgeModule sAtkBridge = {"libatk-bridge-2.0.so.0", nullptr, + "atk_bridge_adaptor_init", nullptr}; + +static nsresult LoadGtkModule(AtkBridgeModule& aModule) { + NS_ENSURE_ARG(aModule.libName); + + if (!(aModule.lib = PR_LoadLibrary(aModule.libName))) { + return NS_ERROR_FAILURE; + } + + // we have loaded the library, try to get the function ptrs + if (!(aModule.init = (AtkBridgeAdaptorInit)PR_FindFunctionSymbol( + aModule.lib, aModule.initName))) { + // fail, :( + PR_UnloadLibrary(aModule.lib); + aModule.lib = nullptr; + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +void a11y::PlatformInit() { + if (!ShouldA11yBeEnabled()) return; + + sATKLib = PR_LoadLibrary(sATKLibName); + if (!sATKLib) return; + + AtkGetTypeType pfn_atk_hyperlink_impl_get_type = + (AtkGetTypeType)PR_FindFunctionSymbol(sATKLib, + sATKHyperlinkImplGetTypeSymbol); + if (pfn_atk_hyperlink_impl_get_type) { + g_atk_hyperlink_impl_type = pfn_atk_hyperlink_impl_get_type(); + } + + gAtkTableCellGetTypeFunc = + (GType(*)())PR_FindFunctionSymbol(sATKLib, "atk_table_cell_get_type"); + + const char* (*atkGetVersion)() = + (const char* (*)())PR_FindFunctionSymbol(sATKLib, "atk_get_version"); + if (atkGetVersion) { + const char* version = atkGetVersion(); + if (version) { + char* endPtr = nullptr; + atkMajorVersion = strtol(version, &endPtr, 10); + if (atkMajorVersion != 0L) { + atkMinorVersion = strtol(endPtr + 1, &endPtr, 10); + if (atkMinorVersion != 0L) { + atkMicroVersion = strtol(endPtr + 1, &endPtr, 10); + } + } + } + } + + // Initialize the MAI Utility class, it will overwrite gail_util. + g_type_class_unref(g_type_class_ref(mai_util_get_type())); + + // Init atk-bridge now + PR_SetEnv("NO_AT_BRIDGE=0"); + nsresult rv = LoadGtkModule(sAtkBridge); + if (NS_SUCCEEDED(rv)) { + (*sAtkBridge.init)(nullptr, nullptr); + } + + if (!sToplevel_event_hook_added) { + sToplevel_event_hook_added = true; + sToplevel_show_hook = g_signal_add_emission_hook( + g_signal_lookup("show", GTK_TYPE_WINDOW), 0, toplevel_event_watcher, + reinterpret_cast<gpointer>(nsIAccessibleEvent::EVENT_SHOW), nullptr); + sToplevel_hide_hook = g_signal_add_emission_hook( + g_signal_lookup("hide", GTK_TYPE_WINDOW), 0, toplevel_event_watcher, + reinterpret_cast<gpointer>(nsIAccessibleEvent::EVENT_HIDE), nullptr); + } +} + +void a11y::PlatformShutdown() { + if (sToplevel_event_hook_added) { + sToplevel_event_hook_added = false; + g_signal_remove_emission_hook(g_signal_lookup("show", GTK_TYPE_WINDOW), + sToplevel_show_hook); + g_signal_remove_emission_hook(g_signal_lookup("hide", GTK_TYPE_WINDOW), + sToplevel_hide_hook); + } + + if (sAtkBridge.lib) { + // Do not shutdown/unload atk-bridge, + // an exit function registered will take care of it + // PR_UnloadLibrary(sAtkBridge.lib); + sAtkBridge.lib = nullptr; + sAtkBridge.init = nullptr; + } + // if (sATKLib) { + // PR_UnloadLibrary(sATKLib); + // sATKLib = nullptr; + // } +} + +static const char sAccEnv[] = "GNOME_ACCESSIBILITY"; +#ifdef MOZ_ENABLE_DBUS +static DBusPendingCall* sPendingCall = nullptr; +#endif + +void a11y::PreInit() { +#ifdef MOZ_ENABLE_DBUS + static bool sChecked = FALSE; + if (sChecked) return; + + sChecked = TRUE; + + // dbus is only checked if GNOME_ACCESSIBILITY is unset + // also make sure that a session bus address is available to prevent dbus from + // starting a new one. Dbus confuses the test harness when it creates a new + // process (see bug 693343) + if (PR_GetEnv(sAccEnv) || !PR_GetEnv("DBUS_SESSION_BUS_ADDRESS")) return; + + DBusConnection* bus = dbus_bus_get(DBUS_BUS_SESSION, nullptr); + if (!bus) return; + + dbus_connection_set_exit_on_disconnect(bus, FALSE); + + static const char* iface = "org.a11y.Status"; + static const char* member = "IsEnabled"; + DBusMessage* message; + message = + dbus_message_new_method_call("org.a11y.Bus", "/org/a11y/bus", + "org.freedesktop.DBus.Properties", "Get"); + if (!message) goto dbus_done; + + dbus_message_append_args(message, DBUS_TYPE_STRING, &iface, DBUS_TYPE_STRING, + &member, DBUS_TYPE_INVALID); + dbus_connection_send_with_reply(bus, message, &sPendingCall, 1000); + dbus_message_unref(message); + +dbus_done: + dbus_connection_unref(bus); +#endif +} + +bool a11y::ShouldA11yBeEnabled() { + static bool sChecked = false, sShouldEnable = false; + if (sChecked) return sShouldEnable; + + sChecked = true; + + EPlatformDisabledState disabledState = PlatformDisabledState(); + if (disabledState == ePlatformIsDisabled) { + return sShouldEnable = false; + } + if (disabledState == ePlatformIsForceEnabled) { + return sShouldEnable = true; + } + + // check if accessibility enabled/disabled by environment variable + const char* envValue = PR_GetEnv(sAccEnv); + if (envValue) return sShouldEnable = !!atoi(envValue); + +#ifdef MOZ_ENABLE_DBUS + PreInit(); + bool dbusSuccess = false; + DBusMessage* reply = nullptr; + if (!sPendingCall) goto dbus_done; + + dbus_pending_call_block(sPendingCall); + reply = dbus_pending_call_steal_reply(sPendingCall); + dbus_pending_call_unref(sPendingCall); + sPendingCall = nullptr; + if (!reply || + dbus_message_get_type(reply) != DBUS_MESSAGE_TYPE_METHOD_RETURN || + strcmp(dbus_message_get_signature(reply), DBUS_TYPE_VARIANT_AS_STRING)) { + goto dbus_done; + } + + DBusMessageIter iter, iter_variant, iter_struct; + dbus_bool_t dResult; + dbus_message_iter_init(reply, &iter); + dbus_message_iter_recurse(&iter, &iter_variant); + switch (dbus_message_iter_get_arg_type(&iter_variant)) { + case DBUS_TYPE_STRUCT: + // at-spi2-core 2.2.0-2.2.1 had a bug where it returned a struct + dbus_message_iter_recurse(&iter_variant, &iter_struct); + if (dbus_message_iter_get_arg_type(&iter_struct) == DBUS_TYPE_BOOLEAN) { + dbus_message_iter_get_basic(&iter_struct, &dResult); + sShouldEnable = dResult; + dbusSuccess = true; + } + + break; + case DBUS_TYPE_BOOLEAN: + dbus_message_iter_get_basic(&iter_variant, &dResult); + sShouldEnable = dResult; + dbusSuccess = true; + break; + default: + break; + } + +dbus_done: + if (reply) dbus_message_unref(reply); + + if (dbusSuccess) return sShouldEnable; +#endif + +// check GSettings +#define GSETINGS_A11Y_INTERFACE "org.gnome.desktop.interface" +#define GSETINGS_A11Y_KEY "toolkit-accessibility" + nsCOMPtr<nsIGSettingsService> gsettings = + do_GetService(NS_GSETTINGSSERVICE_CONTRACTID); + nsCOMPtr<nsIGSettingsCollection> a11y_settings; + + if (gsettings) { + gsettings->GetCollectionForSchema(nsLiteralCString(GSETINGS_A11Y_INTERFACE), + getter_AddRefs(a11y_settings)); + if (a11y_settings) { + a11y_settings->GetBoolean(nsLiteralCString(GSETINGS_A11Y_KEY), + &sShouldEnable); + } + } + + return sShouldEnable; +} diff --git a/accessible/atk/RootAccessibleWrap.cpp b/accessible/atk/RootAccessibleWrap.cpp new file mode 100644 index 0000000000..41916a0055 --- /dev/null +++ b/accessible/atk/RootAccessibleWrap.cpp @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RootAccessibleWrap.h" + +#include "atk/atkobject.h" +#include "nsTArray.h" + +#include <glib-object.h> + +using namespace mozilla::a11y; + +GtkWindowAccessible::GtkWindowAccessible(AtkObject* aAccessible) { + g_object_ref(aAccessible); + mAtkObject = aAccessible; +} + +GtkWindowAccessible::~GtkWindowAccessible() { + g_object_unref(mAtkObject); + mAtkObject = nullptr; +} diff --git a/accessible/atk/RootAccessibleWrap.h b/accessible/atk/RootAccessibleWrap.h new file mode 100644 index 0000000000..75038f698d --- /dev/null +++ b/accessible/atk/RootAccessibleWrap.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_a11y_RootAccessibleWrap_h__ +#define mozilla_a11y_RootAccessibleWrap_h__ + +#include "BaseAccessibles.h" +#include "RootAccessible.h" + +namespace mozilla { +namespace a11y { + +typedef RootAccessible RootAccessibleWrap; + +/* GtkWindowAccessible is the accessible class for gtk+ native window. + * The instance of GtkWindowAccessible is a child of MaiAppRoot instance. + * It is added into root when the toplevel window is created, and removed + * from root when the toplevel window is destroyed. + */ +class GtkWindowAccessible final : public DummyAccessible { + public: + explicit GtkWindowAccessible(AtkObject* aAccessible); + virtual ~GtkWindowAccessible(); +}; + +} // namespace a11y +} // namespace mozilla + +#endif /* mozilla_a11y_Root_Accessible_Wrap_h__ */ diff --git a/accessible/atk/UtilInterface.cpp b/accessible/atk/UtilInterface.cpp new file mode 100644 index 0000000000..8389e09f80 --- /dev/null +++ b/accessible/atk/UtilInterface.cpp @@ -0,0 +1,347 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ApplicationAccessible.h" +#include "mozilla/Likely.h" +#include "nsAccessibilityService.h" +#include "nsMai.h" + +#include <atk/atkobject.h> +#include <atk/atkutil.h> +#include <gtk/gtk.h> +#include <string.h> + +using namespace mozilla; +using namespace mozilla::a11y; + +typedef AtkUtil MaiUtil; +typedef AtkUtilClass MaiUtilClass; + +#define MAI_VERSION MOZILLA_VERSION +#define MAI_NAME "Gecko" + +extern "C" { +static guint (*gail_add_global_event_listener)(GSignalEmissionHook listener, + const gchar* event_type); +static void (*gail_remove_global_event_listener)(guint remove_listener); +static void (*gail_remove_key_event_listener)(guint remove_listener); +static AtkObject* (*gail_get_root)(); +} + +struct MaiUtilListenerInfo { + gint key; + guint signal_id; + gulong hook_id; + // For window create/destory/minimize/maximize/restore/activate/deactivate + // events, we'll chain gail_util's add/remove_global_event_listener. + // So we store the listenerid returned by gail's add_global_event_listener + // in this structure to call gail's remove_global_event_listener later. + guint gail_listenerid; +}; + +static GHashTable* sListener_list = nullptr; +static gint sListener_idx = 1; + +extern "C" { +static guint add_listener(GSignalEmissionHook listener, + const gchar* object_type, const gchar* signal, + const gchar* hook_data, guint gail_listenerid = 0) { + GType type; + guint signal_id; + gint rc = 0; + + type = g_type_from_name(object_type); + if (type) { + signal_id = g_signal_lookup(signal, type); + if (signal_id > 0) { + MaiUtilListenerInfo* listener_info; + + rc = sListener_idx; + + listener_info = + (MaiUtilListenerInfo*)g_malloc(sizeof(MaiUtilListenerInfo)); + listener_info->key = sListener_idx; + listener_info->hook_id = g_signal_add_emission_hook( + signal_id, 0, listener, g_strdup(hook_data), (GDestroyNotify)g_free); + listener_info->signal_id = signal_id; + listener_info->gail_listenerid = gail_listenerid; + + g_hash_table_insert(sListener_list, &(listener_info->key), listener_info); + sListener_idx++; + } else { + g_warning("Invalid signal type %s\n", signal); + } + } else { + g_warning("Invalid object type %s\n", object_type); + } + return rc; +} + +static guint mai_util_add_global_event_listener(GSignalEmissionHook listener, + const gchar* event_type) { + guint rc = 0; + gchar** split_string; + + split_string = g_strsplit(event_type, ":", 3); + + if (split_string) { + if (!strcmp("window", split_string[0])) { + guint gail_listenerid = 0; + if (gail_add_global_event_listener) { + // call gail's function to track gtk native window events + gail_listenerid = gail_add_global_event_listener(listener, event_type); + } + + rc = add_listener(listener, "MaiAtkObject", split_string[1], event_type, + gail_listenerid); + } else { + rc = add_listener(listener, split_string[1], split_string[2], event_type); + } + g_strfreev(split_string); + } + return rc; +} + +static void mai_util_remove_global_event_listener(guint remove_listener) { + if (remove_listener > 0) { + MaiUtilListenerInfo* listener_info; + gint tmp_idx = remove_listener; + + listener_info = + (MaiUtilListenerInfo*)g_hash_table_lookup(sListener_list, &tmp_idx); + + if (listener_info != nullptr) { + if (gail_remove_global_event_listener && listener_info->gail_listenerid) { + gail_remove_global_event_listener(listener_info->gail_listenerid); + } + + /* Hook id of 0 and signal id of 0 are invalid */ + if (listener_info->hook_id != 0 && listener_info->signal_id != 0) { + /* Remove the emission hook */ + g_signal_remove_emission_hook(listener_info->signal_id, + listener_info->hook_id); + + /* Remove the element from the hash */ + g_hash_table_remove(sListener_list, &tmp_idx); + } else { + g_warning("Invalid listener hook_id %ld or signal_id %d\n", + listener_info->hook_id, listener_info->signal_id); + } + } else { + // atk-bridge is initialized with gail (e.g. yelp) + // try gail_remove_global_event_listener + if (gail_remove_global_event_listener) { + return gail_remove_global_event_listener(remove_listener); + } + + g_warning("No listener with the specified listener id %d", + remove_listener); + } + } else { + g_warning("Invalid listener_id %d", remove_listener); + } +} + +static AtkKeyEventStruct* atk_key_event_from_gdk_event_key(GdkEventKey* key) { + AtkKeyEventStruct* event = g_new0(AtkKeyEventStruct, 1); + switch (key->type) { + case GDK_KEY_PRESS: + event->type = ATK_KEY_EVENT_PRESS; + break; + case GDK_KEY_RELEASE: + event->type = ATK_KEY_EVENT_RELEASE; + break; + default: + g_assert_not_reached(); + return nullptr; + } + event->state = key->state; + event->keyval = key->keyval; + event->length = key->length; + if (key->string && key->string[0] && + g_unichar_isgraph(g_utf8_get_char(key->string))) { + event->string = key->string; + } else if (key->type == GDK_KEY_PRESS || key->type == GDK_KEY_RELEASE) { + event->string = gdk_keyval_name(key->keyval); + } + event->keycode = key->hardware_keycode; + event->timestamp = key->time; + + return event; +} + +struct MaiKeyEventInfo { + AtkKeyEventStruct* key_event; + gpointer func_data; +}; + +union AtkKeySnoopFuncPointer { + AtkKeySnoopFunc func_ptr; + gpointer data; +}; + +static gboolean notify_hf(gpointer key, gpointer value, gpointer data) { + MaiKeyEventInfo* info = (MaiKeyEventInfo*)data; + AtkKeySnoopFuncPointer atkKeySnoop; + atkKeySnoop.data = value; + return (atkKeySnoop.func_ptr)(info->key_event, info->func_data) ? TRUE + : FALSE; +} + +static void insert_hf(gpointer key, gpointer value, gpointer data) { + GHashTable* new_table = (GHashTable*)data; + g_hash_table_insert(new_table, key, value); +} + +static GHashTable* sKey_listener_list = nullptr; + +static gint mai_key_snooper(GtkWidget* the_widget, GdkEventKey* event, + gpointer func_data) { + /* notify each AtkKeySnoopFunc in turn... */ + + MaiKeyEventInfo* info = g_new0(MaiKeyEventInfo, 1); + gint consumed = 0; + if (sKey_listener_list) { + GHashTable* new_hash = g_hash_table_new(nullptr, nullptr); + g_hash_table_foreach(sKey_listener_list, insert_hf, new_hash); + info->key_event = atk_key_event_from_gdk_event_key(event); + info->func_data = func_data; + consumed = g_hash_table_foreach_steal(new_hash, notify_hf, info); + g_hash_table_destroy(new_hash); + g_free(info->key_event); + } + g_free(info); + return (consumed ? 1 : 0); +} + +static guint sKey_snooper_id = 0; + +static guint mai_util_add_key_event_listener(AtkKeySnoopFunc listener, + gpointer data) { + if (MOZ_UNLIKELY(!listener)) { + return 0; + } + + static guint key = 0; + + if (!sKey_listener_list) { + sKey_listener_list = g_hash_table_new(nullptr, nullptr); + } + + // If we have no registered event listeners then we need to (re)install the + // key event snooper. + if (g_hash_table_size(sKey_listener_list) == 0) { + sKey_snooper_id = gtk_key_snooper_install(mai_key_snooper, data); + } + + AtkKeySnoopFuncPointer atkKeySnoop; + atkKeySnoop.func_ptr = listener; + key++; + g_hash_table_insert(sKey_listener_list, GUINT_TO_POINTER(key), + atkKeySnoop.data); + return key; +} + +static void mai_util_remove_key_event_listener(guint remove_listener) { + if (!sKey_listener_list) { + // atk-bridge is initialized with gail (e.g. yelp) + // try gail_remove_key_event_listener + return gail_remove_key_event_listener(remove_listener); + } + + g_hash_table_remove(sKey_listener_list, GUINT_TO_POINTER(remove_listener)); + if (g_hash_table_size(sKey_listener_list) == 0) { + gtk_key_snooper_remove(sKey_snooper_id); + } +} + +static AtkObject* mai_util_get_root() { + ApplicationAccessible* app = ApplicationAcc(); + if (app) return app->GetAtkObject(); + + // We've shutdown, try to use gail instead + // (to avoid assert in spi_atk_tidy_windows()) + // XXX tbsaunde then why didn't we replace the gail atk_util impl? + if (gail_get_root) return gail_get_root(); + + return nullptr; +} + +static const gchar* mai_util_get_toolkit_name() { return MAI_NAME; } + +static const gchar* mai_util_get_toolkit_version() { return MAI_VERSION; } + +static void _listener_info_destroy(gpointer data) { g_free(data); } + +static void window_added(AtkObject* atk_obj, guint index, AtkObject* child) { + if (!IS_MAI_OBJECT(child)) return; + + static guint id = g_signal_lookup("create", MAI_TYPE_ATK_OBJECT); + g_signal_emit(child, id, 0); +} + +static void window_removed(AtkObject* atk_obj, guint index, AtkObject* child) { + if (!IS_MAI_OBJECT(child)) return; + + static guint id = g_signal_lookup("destroy", MAI_TYPE_ATK_OBJECT); + g_signal_emit(child, id, 0); +} + +static void UtilInterfaceInit(MaiUtilClass* klass) { + AtkUtilClass* atk_class; + gpointer data; + + data = g_type_class_peek(ATK_TYPE_UTIL); + atk_class = ATK_UTIL_CLASS(data); + + // save gail function pointer + gail_add_global_event_listener = atk_class->add_global_event_listener; + gail_remove_global_event_listener = atk_class->remove_global_event_listener; + gail_remove_key_event_listener = atk_class->remove_key_event_listener; + gail_get_root = atk_class->get_root; + + atk_class->add_global_event_listener = mai_util_add_global_event_listener; + atk_class->remove_global_event_listener = + mai_util_remove_global_event_listener; + atk_class->add_key_event_listener = mai_util_add_key_event_listener; + atk_class->remove_key_event_listener = mai_util_remove_key_event_listener; + atk_class->get_root = mai_util_get_root; + atk_class->get_toolkit_name = mai_util_get_toolkit_name; + atk_class->get_toolkit_version = mai_util_get_toolkit_version; + + sListener_list = g_hash_table_new_full(g_int_hash, g_int_equal, nullptr, + _listener_info_destroy); + // Keep track of added/removed windows. + AtkObject* root = atk_get_root(); + g_signal_connect(root, "children-changed::add", (GCallback)window_added, + nullptr); + g_signal_connect(root, "children-changed::remove", (GCallback)window_removed, + nullptr); +} +} + +GType mai_util_get_type() { + static GType type = 0; + + if (!type) { + static const GTypeInfo tinfo = { + sizeof(MaiUtilClass), + (GBaseInitFunc) nullptr, /* base init */ + (GBaseFinalizeFunc) nullptr, /* base finalize */ + (GClassInitFunc)UtilInterfaceInit, /* class init */ + (GClassFinalizeFunc) nullptr, /* class finalize */ + nullptr, /* class data */ + sizeof(MaiUtil), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc) nullptr, /* instance init */ + nullptr /* value table */ + }; + + type = + g_type_register_static(ATK_TYPE_UTIL, "MaiUtil", &tinfo, GTypeFlags(0)); + } + return type; +} diff --git a/accessible/atk/moz.build b/accessible/atk/moz.build new file mode 100644 index 0000000000..092ecada57 --- /dev/null +++ b/accessible/atk/moz.build @@ -0,0 +1,64 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS.mozilla.a11y += [ + "AccessibleWrap.h", +] + +SOURCES += [ + "AccessibleWrap.cpp", + "ApplicationAccessibleWrap.cpp", + "DocAccessibleWrap.cpp", + "DOMtoATK.cpp", + "nsMaiHyperlink.cpp", + "nsMaiInterfaceAction.cpp", + "nsMaiInterfaceComponent.cpp", + "nsMaiInterfaceDocument.cpp", + "nsMaiInterfaceEditableText.cpp", + "nsMaiInterfaceHyperlinkImpl.cpp", + "nsMaiInterfaceHypertext.cpp", + "nsMaiInterfaceImage.cpp", + "nsMaiInterfaceSelection.cpp", + "nsMaiInterfaceTable.cpp", + "nsMaiInterfaceTableCell.cpp", + "nsMaiInterfaceText.cpp", + "nsMaiInterfaceValue.cpp", + "Platform.cpp", + "RootAccessibleWrap.cpp", + "UtilInterface.cpp", +] + +LOCAL_INCLUDES += [ + "/accessible/base", + "/accessible/generic", + "/accessible/html", + "/accessible/ipc", + "/accessible/xpcom", + "/accessible/xul", + "/layout/generic", + "/other-licenses/atk-1.0", + "/widget", + "/widget/gtk", +] + +FINAL_LIBRARY = "xul" + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + CFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] + +if CONFIG["MOZ_ENABLE_DBUS"]: + CXXFLAGS += CONFIG["MOZ_DBUS_CFLAGS"] + +include("/ipc/chromium/chromium-config.mozbuild") + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + # Used in G_DEFINE_TYPE_EXTENDED macro, probably fixed in newer glib / + # gobject headers. See bug 1243331 comment 3. + CXXFLAGS += [ + "-Wno-error=unused-function", + "-Wno-unused-local-typedefs", + ] diff --git a/accessible/atk/nsMai.h b/accessible/atk/nsMai.h new file mode 100644 index 0000000000..175a3c64ff --- /dev/null +++ b/accessible/atk/nsMai.h @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __NS_MAI_H__ +#define __NS_MAI_H__ + +#include <atk/atk.h> +#include <glib.h> +#include <glib-object.h> + +#include "AccessibleWrap.h" + +namespace mozilla { +namespace a11y { +class RemoteAccessible; +class Accessible; +} // namespace a11y +} // namespace mozilla + +#define MAI_TYPE_ATK_OBJECT (mai_atk_object_get_type()) +#define MAI_ATK_OBJECT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MAI_TYPE_ATK_OBJECT, MaiAtkObject)) +#define MAI_ATK_OBJECT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), MAI_TYPE_ATK_OBJECT, MaiAtkObjectClass)) +#define IS_MAI_OBJECT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), MAI_TYPE_ATK_OBJECT)) +#define IS_MAI_OBJECT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), MAI_TYPE_ATK_OBJECT)) +#define MAI_ATK_OBJECT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MAI_TYPE_ATK_OBJECT, MaiAtkObjectClass)) +GType mai_atk_object_get_type(void); +GType mai_util_get_type(); + +// This is a pointer to the atk_table_cell_get_type function if we are using +// a version of atk that defines that. +extern "C" GType (*gAtkTableCellGetTypeFunc)(); + +mozilla::a11y::AccessibleWrap* GetAccessibleWrap(AtkObject* aAtkObj); +mozilla::a11y::RemoteAccessible* GetProxy(AtkObject* aAtkObj); +mozilla::a11y::Accessible* GetInternalObj(AtkObject* aObj); +AtkObject* GetWrapperFor(mozilla::a11y::Accessible* acc); + +extern int atkMajorVersion, atkMinorVersion, atkMicroVersion; + +/** + * Return true if the loaded version of libatk-1.0.so is at least + * aMajor.aMinor.aMicro. + */ +static inline bool IsAtkVersionAtLeast(int aMajor, int aMinor, int aMicro = 0) { + return aMajor < atkMajorVersion || + (aMajor == atkMajorVersion && + (aMinor < atkMinorVersion || + (aMinor == atkMinorVersion && aMicro <= atkMicroVersion))); +} + +/** + * This MaiAtkObject is a thin wrapper, in the MAI namespace, for AtkObject + */ +struct MaiAtkObject { + AtkObject parent; + /* + * The AccessibleWrap whose properties and features are exported + * via this object instance. + */ + mozilla::a11y::Accessible* acc; + + /* + * Get the AtkHyperlink for this atk object. + */ + AtkHyperlink* GetAtkHyperlink(); + + /* + * Shutdown this AtkObject. + */ + void Shutdown(); + + /* + * Notify atk of a state change on this AtkObject. + */ + void FireStateChangeEvent(uint64_t aState, bool aEnabled); + + /* + * Notify ATK of a text change within this ATK object. + */ + void FireTextChangeEvent(const nsAString& aStr, int32_t aStart, uint32_t aLen, + bool aIsInsert, bool aIsFromUser); + + /** + * Notify ATK of a shown or hidden subtree rooted at aObject whose parent is + * aParent + */ + void FireAtkShowHideEvent(AtkObject* aParent, bool aIsAdded, bool aFromUser); + + private: + /* + * do we have text-remove and text-insert signals if not we need to use + * text-changed see AccessibleWrap::FireAtkTextChangedEvent() and + * bug 619002 + */ + enum EAvailableAtkSignals { + eUnknown, + eHaveNewAtkTextSignals, + eNoNewAtkSignals + }; + + static EAvailableAtkSignals gAvailableAtkSignals; +}; + +#endif /* __NS_MAI_H__ */ diff --git a/accessible/atk/nsMaiHyperlink.cpp b/accessible/atk/nsMaiHyperlink.cpp new file mode 100644 index 0000000000..65f223e528 --- /dev/null +++ b/accessible/atk/nsMaiHyperlink.cpp @@ -0,0 +1,216 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIURI.h" +#include "nsMaiHyperlink.h" +#include "mozilla/a11y/RemoteAccessible.h" + +using namespace mozilla::a11y; + +/* MaiAtkHyperlink */ + +#define MAI_TYPE_ATK_HYPERLINK (mai_atk_hyperlink_get_type()) +#define MAI_ATK_HYPERLINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MAI_TYPE_ATK_HYPERLINK, MaiAtkHyperlink)) +#define MAI_ATK_HYPERLINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), MAI_TYPE_ATK_HYPERLINK, \ + MaiAtkHyperlinkClass)) +#define MAI_IS_ATK_HYPERLINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), MAI_TYPE_ATK_HYPERLINK)) +#define MAI_IS_ATK_HYPERLINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), MAI_TYPE_ATK_HYPERLINK)) +#define MAI_ATK_HYPERLINK_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MAI_TYPE_ATK_HYPERLINK, \ + MaiAtkHyperlinkClass)) + +/** + * This MaiAtkHyperlink is a thin wrapper, in the MAI namespace, + * for AtkHyperlink + */ + +struct MaiAtkHyperlink { + AtkHyperlink parent; + + /* + * The MaiHyperlink whose properties and features are exported via this + * hyperlink instance. + */ + MaiHyperlink* maiHyperlink; +}; + +struct MaiAtkHyperlinkClass { + AtkHyperlinkClass parent_class; +}; + +GType mai_atk_hyperlink_get_type(void); + +G_BEGIN_DECLS +/* callbacks for AtkHyperlink */ +static void classInitCB(AtkHyperlinkClass* aClass); +static void finalizeCB(GObject* aObj); + +/* callbacks for AtkHyperlink virtual functions */ +static gchar* getUriCB(AtkHyperlink* aLink, gint aLinkIndex); +static AtkObject* getObjectCB(AtkHyperlink* aLink, gint aLinkIndex); +static gint getEndIndexCB(AtkHyperlink* aLink); +static gint getStartIndexCB(AtkHyperlink* aLink); +static gboolean isValidCB(AtkHyperlink* aLink); +static gint getAnchorCountCB(AtkHyperlink* aLink); +G_END_DECLS + +static gpointer parent_class = nullptr; + +static MaiHyperlink* GetMaiHyperlink(AtkHyperlink* aHyperlink) { + NS_ENSURE_TRUE(MAI_IS_ATK_HYPERLINK(aHyperlink), nullptr); + MaiHyperlink* maiHyperlink = MAI_ATK_HYPERLINK(aHyperlink)->maiHyperlink; + NS_ENSURE_TRUE(maiHyperlink != nullptr, nullptr); + NS_ENSURE_TRUE(maiHyperlink->GetAtkHyperlink() == aHyperlink, nullptr); + return maiHyperlink; +} + +GType mai_atk_hyperlink_get_type(void) { + static GType type = 0; + + if (!type) { + static const GTypeInfo tinfo = { + sizeof(MaiAtkHyperlinkClass), + (GBaseInitFunc) nullptr, + (GBaseFinalizeFunc) nullptr, + (GClassInitFunc)classInitCB, + (GClassFinalizeFunc) nullptr, + nullptr, /* class data */ + sizeof(MaiAtkHyperlink), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc) nullptr, + nullptr /* value table */ + }; + + type = g_type_register_static(ATK_TYPE_HYPERLINK, "MaiAtkHyperlink", &tinfo, + GTypeFlags(0)); + } + return type; +} + +MaiHyperlink::MaiHyperlink(Accessible* aHyperLink) + : mHyperlink(aHyperLink), mMaiAtkHyperlink(nullptr) { + mMaiAtkHyperlink = reinterpret_cast<AtkHyperlink*>( + g_object_new(mai_atk_hyperlink_get_type(), nullptr)); + NS_ASSERTION(mMaiAtkHyperlink, "OUT OF MEMORY"); + if (!mMaiAtkHyperlink) return; + + MAI_ATK_HYPERLINK(mMaiAtkHyperlink)->maiHyperlink = this; +} + +MaiHyperlink::~MaiHyperlink() { + if (mMaiAtkHyperlink) { + MAI_ATK_HYPERLINK(mMaiAtkHyperlink)->maiHyperlink = nullptr; + g_object_unref(mMaiAtkHyperlink); + } +} + +/* static functions for ATK callbacks */ + +void classInitCB(AtkHyperlinkClass* aClass) { + GObjectClass* gobject_class = G_OBJECT_CLASS(aClass); + + parent_class = g_type_class_peek_parent(aClass); + + aClass->get_uri = getUriCB; + aClass->get_object = getObjectCB; + aClass->get_end_index = getEndIndexCB; + aClass->get_start_index = getStartIndexCB; + aClass->is_valid = isValidCB; + aClass->get_n_anchors = getAnchorCountCB; + + gobject_class->finalize = finalizeCB; +} + +void finalizeCB(GObject* aObj) { + NS_ASSERTION(MAI_IS_ATK_HYPERLINK(aObj), "Invalid MaiAtkHyperlink"); + if (!MAI_IS_ATK_HYPERLINK(aObj)) return; + + MaiAtkHyperlink* maiAtkHyperlink = MAI_ATK_HYPERLINK(aObj); + maiAtkHyperlink->maiHyperlink = nullptr; + + /* call parent finalize function */ + if (G_OBJECT_CLASS(parent_class)->finalize) { + G_OBJECT_CLASS(parent_class)->finalize(aObj); + } +} + +gchar* getUriCB(AtkHyperlink* aLink, gint aLinkIndex) { + MaiHyperlink* maiLink = GetMaiHyperlink(aLink); + if (!maiLink) { + return nullptr; + } + + Accessible* acc = maiLink->Acc(); + if (!acc) { + return nullptr; + } + + nsAutoCString cautoStr; + nsCOMPtr<nsIURI> uri = acc->AnchorURIAt(aLinkIndex); + if (!uri) return nullptr; + + nsresult rv = uri->GetSpec(cautoStr); + NS_ENSURE_SUCCESS(rv, nullptr); + + return g_strdup(cautoStr.get()); +} + +AtkObject* getObjectCB(AtkHyperlink* aLink, gint aLinkIndex) { + MaiHyperlink* maiLink = GetMaiHyperlink(aLink); + if (!maiLink) { + return nullptr; + } + + Accessible* acc = maiLink->Acc(); + if (!acc) { + return nullptr; + } + + Accessible* anchor = acc->AnchorAt(aLinkIndex); + return anchor ? GetWrapperFor(anchor) : nullptr; +} + +gint getEndIndexCB(AtkHyperlink* aLink) { + MaiHyperlink* maiLink = GetMaiHyperlink(aLink); + if (!maiLink) return false; + + return static_cast<gint>(maiLink->Acc()->EndOffset()); +} + +gint getStartIndexCB(AtkHyperlink* aLink) { + MaiHyperlink* maiLink = GetMaiHyperlink(aLink); + if (!maiLink) return -1; + + return static_cast<gint>(maiLink->Acc()->StartOffset()); +} + +gboolean isValidCB(AtkHyperlink* aLink) { + MaiHyperlink* maiLink = GetMaiHyperlink(aLink); + if (!maiLink) return false; + + Accessible* acc = maiLink->Acc(); + if (!acc) { + return false; + } + + return static_cast<gboolean>(acc->IsLinkValid()); +} + +gint getAnchorCountCB(AtkHyperlink* aLink) { + MaiHyperlink* maiLink = GetMaiHyperlink(aLink); + if (!maiLink) return -1; + + Accessible* acc = maiLink->Acc(); + if (!acc) { + return -1; + } + + return static_cast<gint>(acc->AnchorCount()); +} diff --git a/accessible/atk/nsMaiHyperlink.h b/accessible/atk/nsMaiHyperlink.h new file mode 100644 index 0000000000..34f517cc7a --- /dev/null +++ b/accessible/atk/nsMaiHyperlink.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __MAI_HYPERLINK_H__ +#define __MAI_HYPERLINK_H__ + +#include "nsMai.h" +#include "mozilla/a11y/Accessible.h" +#include "mozilla/a11y/LocalAccessible.h" +#include "mozilla/a11y/RemoteAccessible.h" +#include "nsDebug.h" + +struct _AtkHyperlink; +typedef struct _AtkHyperlink AtkHyperlink; + +namespace mozilla { +namespace a11y { + +/* + * MaiHyperlink is a auxiliary class for MaiInterfaceHyperText. + */ + +class MaiHyperlink { + public: + explicit MaiHyperlink(Accessible* aHyperLink); + ~MaiHyperlink(); + + public: + AtkHyperlink* GetAtkHyperlink() const { return mMaiAtkHyperlink; } + Accessible* Acc() { + if (!mHyperlink) { + return nullptr; + } + NS_ASSERTION(mHyperlink->IsLink(), "Why isn't it a link!"); + return mHyperlink; + } + + protected: + Accessible* mHyperlink; + AtkHyperlink* mMaiAtkHyperlink; +}; + +} // namespace a11y +} // namespace mozilla + +#endif /* __MAI_HYPERLINK_H__ */ diff --git a/accessible/atk/nsMaiInterfaceAction.cpp b/accessible/atk/nsMaiInterfaceAction.cpp new file mode 100644 index 0000000000..8149e0aff5 --- /dev/null +++ b/accessible/atk/nsMaiInterfaceAction.cpp @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InterfaceInitFuncs.h" + +#include "LocalAccessible-inl.h" +#include "nsMai.h" +#include "mozilla/Likely.h" +#include "nsAccessibilityService.h" +#include "RemoteAccessible.h" +#include "nsString.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +extern "C" { + +static gboolean doActionCB(AtkAction* aAction, gint aActionIndex) { + AtkObject* atkObject = ATK_OBJECT(aAction); + if (Accessible* acc = GetInternalObj(atkObject)) { + return acc->DoAction(aActionIndex); + } + + return false; +} + +static gint getActionCountCB(AtkAction* aAction) { + AtkObject* atkObject = ATK_OBJECT(aAction); + if (Accessible* acc = GetInternalObj(atkObject)) { + return acc->ActionCount(); + } + + return 0; +} + +static const gchar* getActionDescriptionCB(AtkAction* aAction, + gint aActionIndex) { + AtkObject* atkObject = ATK_OBJECT(aAction); + nsAutoString description; + if (Accessible* acc = GetInternalObj(atkObject)) { + acc->ActionDescriptionAt(aActionIndex, description); + return AccessibleWrap::ReturnString(description); + } + + return nullptr; +} + +static const gchar* getActionNameCB(AtkAction* aAction, gint aActionIndex) { + AtkObject* atkObject = ATK_OBJECT(aAction); + nsAutoString autoStr; + if (Accessible* acc = GetInternalObj(atkObject)) { + acc->ActionNameAt(aActionIndex, autoStr); + return AccessibleWrap::ReturnString(autoStr); + } + + return nullptr; +} + +static const gchar* getKeyBindingCB(AtkAction* aAction, gint aActionIndex) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aAction)); + if (!acc) { + return nullptr; + } + nsAutoString keyBindingsStr; + AccessibleWrap::GetKeyBinding(acc, keyBindingsStr); + + return AccessibleWrap::ReturnString(keyBindingsStr); +} +} + +void actionInterfaceInitCB(AtkActionIface* aIface) { + NS_ASSERTION(aIface, "Invalid aIface"); + if (MOZ_UNLIKELY(!aIface)) return; + + aIface->do_action = doActionCB; + aIface->get_n_actions = getActionCountCB; + aIface->get_description = getActionDescriptionCB; + aIface->get_keybinding = getKeyBindingCB; + aIface->get_name = getActionNameCB; +} diff --git a/accessible/atk/nsMaiInterfaceComponent.cpp b/accessible/atk/nsMaiInterfaceComponent.cpp new file mode 100644 index 0000000000..81e82a8f53 --- /dev/null +++ b/accessible/atk/nsMaiInterfaceComponent.cpp @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InterfaceInitFuncs.h" + +#include "LocalAccessible-inl.h" +#include "AccessibleWrap.h" +#include "nsAccUtils.h" +#include "nsMai.h" +#include "nsWindow.h" +#include "mozilla/Likely.h" +#include "mozilla/a11y/DocAccessibleParent.h" +#include "mozilla/a11y/RemoteAccessible.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/Document.h" +#include "nsAccessibilityService.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +extern "C" { + +static AtkObject* refAccessibleAtPointCB(AtkComponent* aComponent, gint aAccX, + gint aAccY, AtkCoordType aCoordType) { + return refAccessibleAtPointHelper(ATK_OBJECT(aComponent), aAccX, aAccY, + aCoordType); +} + +static void getExtentsCB(AtkComponent* aComponent, gint* aX, gint* aY, + gint* aWidth, gint* aHeight, AtkCoordType aCoordType) { + getExtentsHelper(ATK_OBJECT(aComponent), aX, aY, aWidth, aHeight, aCoordType); +} + +static gboolean grabFocusCB(AtkComponent* aComponent) { + AtkObject* atkObject = ATK_OBJECT(aComponent); + Accessible* acc = GetInternalObj(atkObject); + if (acc) { + acc->TakeFocus(); + return TRUE; + } + return FALSE; +} + +// ScrollType is compatible +MOZ_CAN_RUN_SCRIPT_BOUNDARY +static gboolean scrollToCB(AtkComponent* aComponent, AtkScrollType type) { + AtkObject* atkObject = ATK_OBJECT(aComponent); + if (Accessible* acc = GetInternalObj(atkObject)) { + acc->ScrollTo(type); + return TRUE; + } + + return FALSE; +} + +// CoordType is compatible +static gboolean scrollToPointCB(AtkComponent* aComponent, AtkCoordType coords, + gint x, gint y) { + AtkObject* atkObject = ATK_OBJECT(aComponent); + AccessibleWrap* accWrap = GetAccessibleWrap(atkObject); + if (accWrap) { + accWrap->ScrollToPoint(coords, x, y); + return TRUE; + } + + RemoteAccessible* proxy = GetProxy(atkObject); + if (proxy) { + proxy->ScrollToPoint(coords, x, y); + return TRUE; + } + + return FALSE; +} +} + +AtkObject* refAccessibleAtPointHelper(AtkObject* aAtkObj, gint aX, gint aY, + AtkCoordType aCoordType) { + Accessible* acc = GetInternalObj(aAtkObj); + if (!acc) { + return nullptr; + } + + // Accessible::ChildAtPoint(x,y) is in screen pixels. + if (aCoordType == ATK_XY_WINDOW) { + mozilla::LayoutDeviceIntPoint winCoords = + nsAccUtils::GetScreenCoordsForWindow(acc); + aX += winCoords.x; + aY += winCoords.y; + } + + Accessible* accAtPoint = + acc->ChildAtPoint(aX, aY, Accessible::EWhichChildAtPoint::DeepestChild); + if (!accAtPoint) { + return nullptr; + } + roles::Role role = accAtPoint->Role(); + if (role == roles::TEXT_LEAF || role == roles::STATICTEXT) { + // We don't include text leaf nodes in the ATK tree, so return the parent. + accAtPoint = accAtPoint->Parent(); + MOZ_ASSERT(accAtPoint, "Text leaf should always have a parent"); + } + AtkObject* atkObj = GetWrapperFor(accAtPoint); + if (atkObj) { + g_object_ref(atkObj); + } + return atkObj; +} + +static double getScaleFactor(Accessible* aAccessible) { + DocAccessible* docAcc = nullptr; + if (LocalAccessible* localAcc = aAccessible->AsLocal()) { + docAcc = localAcc->Document(); + } else { + RemoteAccessible* remote = aAccessible->AsRemote(); + LocalAccessible* outerDoc = remote->OuterDocOfRemoteBrowser(); + if (outerDoc) { + docAcc = outerDoc->Document(); + } + } + + if (!docAcc || !docAcc->DocumentNode()) { + return 1.0; + } + + nsCOMPtr<nsIWidget> rootWidget = + nsContentUtils::WidgetForDocument(docAcc->DocumentNode()); + if (!rootWidget) { + return 1.0; + } + + if (RefPtr<nsWindow> window = + static_cast<nsWindow*>(rootWidget->GetTopLevelWidget())) { + return window->FractionalScaleFactor(); + } + + return 1.0; +} + +void getExtentsHelper(AtkObject* aAtkObj, gint* aX, gint* aY, gint* aWidth, + gint* aHeight, AtkCoordType aCoordType) { + *aX = *aY = *aWidth = *aHeight = -1; + + Accessible* acc = GetInternalObj(aAtkObj); + if (!acc) { + return; + } + + mozilla::LayoutDeviceIntRect screenRect = acc->Bounds(); + if (screenRect.IsEmpty()) { + return; + } + + if (aCoordType == ATK_XY_WINDOW) { + mozilla::LayoutDeviceIntPoint winCoords = + nsAccUtils::GetScreenCoordsForWindow(acc); + screenRect.x -= winCoords.x; + screenRect.y -= winCoords.y; + } + + double scaleFactor = getScaleFactor(acc); + + *aX = screenRect.x / scaleFactor; + *aY = screenRect.y / scaleFactor; + *aWidth = screenRect.width / scaleFactor; + *aHeight = screenRect.height / scaleFactor; +} + +void componentInterfaceInitCB(AtkComponentIface* aIface) { + NS_ASSERTION(aIface, "Invalid Interface"); + if (MOZ_UNLIKELY(!aIface)) return; + + /* + * Use default implementation in atk for contains, get_position, + * and get_size + */ + aIface->ref_accessible_at_point = refAccessibleAtPointCB; + aIface->get_extents = getExtentsCB; + aIface->grab_focus = grabFocusCB; + if (IsAtkVersionAtLeast(2, 30)) { + aIface->scroll_to = scrollToCB; + aIface->scroll_to_point = scrollToPointCB; + } +} diff --git a/accessible/atk/nsMaiInterfaceDocument.cpp b/accessible/atk/nsMaiInterfaceDocument.cpp new file mode 100644 index 0000000000..da1bffce37 --- /dev/null +++ b/accessible/atk/nsMaiInterfaceDocument.cpp @@ -0,0 +1,106 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InterfaceInitFuncs.h" + +#include "LocalAccessible-inl.h" +#include "AccessibleWrap.h" +#include "DocAccessible.h" +#include "nsAccUtils.h" +#include "nsMai.h" +#include "RemoteAccessible.h" +#include "mozilla/a11y/DocAccessibleParent.h" +#include "mozilla/Likely.h" + +using namespace mozilla::a11y; + +static const char* const kDocUrlName = "DocURL"; +static const char* const kMimeTypeName = "MimeType"; + +// below functions are vfuncs on an ATK interface so they need to be C call +extern "C" { + +static const gchar* getDocumentLocaleCB(AtkDocument* aDocument); +static AtkAttributeSet* getDocumentAttributesCB(AtkDocument* aDocument); +static const gchar* getDocumentAttributeValueCB(AtkDocument* aDocument, + const gchar* aAttrName); + +void documentInterfaceInitCB(AtkDocumentIface* aIface) { + NS_ASSERTION(aIface, "Invalid Interface"); + if (MOZ_UNLIKELY(!aIface)) return; + + /* + * We don't support get_document or set_attribute right now. + */ + aIface->get_document_attributes = getDocumentAttributesCB; + aIface->get_document_attribute_value = getDocumentAttributeValueCB; + aIface->get_document_locale = getDocumentLocaleCB; +} + +const gchar* getDocumentLocaleCB(AtkDocument* aDocument) { + nsAutoString locale; + Accessible* acc = GetInternalObj(ATK_OBJECT(aDocument)); + if (acc) { + acc->Language(locale); + } + + return locale.IsEmpty() ? nullptr : AccessibleWrap::ReturnString(locale); +} + +static inline GSList* prependToList(GSList* aList, const char* const aName, + const nsAutoString& aValue) { + if (aValue.IsEmpty()) { + return aList; + } + + // libspi will free these + AtkAttribute* atkAttr = (AtkAttribute*)g_malloc(sizeof(AtkAttribute)); + atkAttr->name = g_strdup(aName); + atkAttr->value = g_strdup(NS_ConvertUTF16toUTF8(aValue).get()); + return g_slist_prepend(aList, atkAttr); +} + +AtkAttributeSet* getDocumentAttributesCB(AtkDocument* aDocument) { + nsAutoString url; + nsAutoString mimeType; + Accessible* acc = GetInternalObj(ATK_OBJECT(aDocument)); + + if (!acc || !acc->IsDoc()) { + return nullptr; + } + + nsAccUtils::DocumentURL(acc, url); + nsAccUtils::DocumentMimeType(acc, mimeType); + + // according to atkobject.h, AtkAttributeSet is a GSList + GSList* attributes = nullptr; + attributes = prependToList(attributes, kDocUrlName, url); + attributes = prependToList(attributes, kMimeTypeName, mimeType); + + return attributes; +} + +const gchar* getDocumentAttributeValueCB(AtkDocument* aDocument, + const gchar* aAttrName) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aDocument)); + + if (!acc || !acc->IsDoc()) { + return nullptr; + } + + nsAutoString attrValue; + if (!strcasecmp(aAttrName, kDocUrlName)) { + nsAccUtils::DocumentURL(acc, attrValue); + } else if (!strcasecmp(aAttrName, kMimeTypeName)) { + nsAccUtils::DocumentMimeType(acc, attrValue); + } else { + return nullptr; + } + + return attrValue.IsEmpty() ? nullptr + : AccessibleWrap::ReturnString(attrValue); +} +} diff --git a/accessible/atk/nsMaiInterfaceEditableText.cpp b/accessible/atk/nsMaiInterfaceEditableText.cpp new file mode 100644 index 0000000000..2dc692362e --- /dev/null +++ b/accessible/atk/nsMaiInterfaceEditableText.cpp @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InterfaceInitFuncs.h" + +#include "LocalAccessible-inl.h" +#include "HyperTextAccessible-inl.h" +#include "nsMai.h" +#include "RemoteAccessible.h" +#include "nsString.h" +#include "mozilla/Likely.h" + +using namespace mozilla::a11y; + +extern "C" { +static void setTextContentsCB(AtkEditableText* aText, const gchar* aString) { + if (Accessible* acc = GetInternalObj(ATK_OBJECT(aText))) { + if (acc->IsTextRole()) { + return; + } + if (HyperTextAccessibleBase* text = acc->AsHyperTextBase()) { + NS_ConvertUTF8toUTF16 strContent(aString); + text->ReplaceText(strContent); + } + } +} + +static void insertTextCB(AtkEditableText* aText, const gchar* aString, + gint aLength, gint* aPosition) { + if (Accessible* acc = GetInternalObj(ATK_OBJECT(aText))) { + if (acc->IsTextRole()) { + return; + } + if (HyperTextAccessibleBase* text = acc->AsHyperTextBase()) { + NS_ConvertUTF8toUTF16 strContent(aString); + text->InsertText(strContent, *aPosition); + } + } +} + +static void copyTextCB(AtkEditableText* aText, gint aStartPos, gint aEndPos) { + if (Accessible* acc = GetInternalObj(ATK_OBJECT(aText))) { + if (acc->IsTextRole()) { + return; + } + if (HyperTextAccessibleBase* text = acc->AsHyperTextBase()) { + text->CopyText(aStartPos, aEndPos); + } + } +} + +static void cutTextCB(AtkEditableText* aText, gint aStartPos, gint aEndPos) { + if (Accessible* acc = GetInternalObj(ATK_OBJECT(aText))) { + if (acc->IsTextRole()) { + return; + } + if (HyperTextAccessibleBase* text = acc->AsHyperTextBase()) { + text->CutText(aStartPos, aEndPos); + } + } +} + +static void deleteTextCB(AtkEditableText* aText, gint aStartPos, gint aEndPos) { + if (Accessible* acc = GetInternalObj(ATK_OBJECT(aText))) { + if (acc->IsTextRole()) { + return; + } + if (HyperTextAccessibleBase* text = acc->AsHyperTextBase()) { + text->DeleteText(aStartPos, aEndPos); + } + } +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY +static void pasteTextCB(AtkEditableText* aText, gint aPosition) { + if (Accessible* acc = GetInternalObj(ATK_OBJECT(aText))) { + if (acc->IsTextRole()) { + return; + } + if (HyperTextAccessibleBase* text = acc->AsHyperTextBase()) { + text->PasteText(aPosition); + } + } +} +} + +void editableTextInterfaceInitCB(AtkEditableTextIface* aIface) { + NS_ASSERTION(aIface, "Invalid aIface"); + if (MOZ_UNLIKELY(!aIface)) return; + + aIface->set_text_contents = setTextContentsCB; + aIface->insert_text = insertTextCB; + aIface->copy_text = copyTextCB; + aIface->cut_text = cutTextCB; + aIface->delete_text = deleteTextCB; + aIface->paste_text = pasteTextCB; +} diff --git a/accessible/atk/nsMaiInterfaceHyperlinkImpl.cpp b/accessible/atk/nsMaiInterfaceHyperlinkImpl.cpp new file mode 100644 index 0000000000..ed8c4f4fce --- /dev/null +++ b/accessible/atk/nsMaiInterfaceHyperlinkImpl.cpp @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InterfaceInitFuncs.h" + +#include "nsMaiHyperlink.h" +#include "mozilla/Likely.h" + +using namespace mozilla::a11y; + +extern "C" { +static AtkHyperlink* getHyperlinkCB(AtkHyperlinkImpl* aImpl) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aImpl)); + if (!acc) { + return nullptr; + } + + NS_ASSERTION(acc->IsLink(), "why isn't it a link!"); + + return MAI_ATK_OBJECT(aImpl)->GetAtkHyperlink(); +} +} + +void hyperlinkImplInterfaceInitCB(AtkHyperlinkImplIface* aIface) { + NS_ASSERTION(aIface, "no interface!"); + if (MOZ_UNLIKELY(!aIface)) return; + + aIface->get_hyperlink = getHyperlinkCB; +} diff --git a/accessible/atk/nsMaiInterfaceHypertext.cpp b/accessible/atk/nsMaiInterfaceHypertext.cpp new file mode 100644 index 0000000000..1e073c87d2 --- /dev/null +++ b/accessible/atk/nsMaiInterfaceHypertext.cpp @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InterfaceInitFuncs.h" + +#include "LocalAccessible-inl.h" +#include "HyperTextAccessible.h" +#include "nsMai.h" +#include "nsMaiHyperlink.h" +#include "RemoteAccessible.h" +#include "mozilla/Likely.h" + +using namespace mozilla::a11y; + +extern "C" { + +static AtkHyperlink* getLinkCB(AtkHypertext* aText, gint aLinkIndex) { + if (Accessible* acc = GetInternalObj(ATK_OBJECT(aText))) { + if (HyperTextAccessibleBase* hyperText = acc->AsHyperTextBase()) { + Accessible* linkAcc = hyperText->LinkAt(aLinkIndex); + AtkObject* atkHyperLink = GetWrapperFor(linkAcc); + NS_ENSURE_TRUE(IS_MAI_OBJECT(atkHyperLink), nullptr); + return MAI_ATK_OBJECT(atkHyperLink)->GetAtkHyperlink(); + } + } + + return nullptr; +} + +static gint getLinkCountCB(AtkHypertext* aText) { + if (Accessible* acc = GetInternalObj(ATK_OBJECT(aText))) { + if (HyperTextAccessibleBase* hyperText = acc->AsHyperTextBase()) { + return static_cast<gint>(hyperText->LinkCount()); + } + } + return -1; +} + +static gint getLinkIndexCB(AtkHypertext* aText, gint aCharIndex) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc) { + return -1; + } + HyperTextAccessibleBase* hyperText = acc->AsHyperTextBase(); + if (!hyperText) { + return -1; + } + return hyperText->LinkIndexAtOffset(aCharIndex); +} + +} // extern "C" + +void hypertextInterfaceInitCB(AtkHypertextIface* aIface) { + NS_ASSERTION(aIface, "no interface!"); + if (MOZ_UNLIKELY(!aIface)) return; + + aIface->get_link = getLinkCB; + aIface->get_n_links = getLinkCountCB; + aIface->get_link_index = getLinkIndexCB; +} diff --git a/accessible/atk/nsMaiInterfaceImage.cpp b/accessible/atk/nsMaiInterfaceImage.cpp new file mode 100644 index 0000000000..dee28f109f --- /dev/null +++ b/accessible/atk/nsMaiInterfaceImage.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InterfaceInitFuncs.h" + +#include "AccessibleWrap.h" +#include "mozilla/a11y/Accessible.h" +#include "mozilla/Likely.h" +#include "nsMai.h" +#include "nsIAccessibleTypes.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +extern "C" { +const gchar* getDescriptionCB(AtkObject* aAtkObj); + +static void getImagePositionCB(AtkImage* aImage, gint* aAccX, gint* aAccY, + AtkCoordType aCoordType) { + LayoutDeviceIntPoint pos(-1, -1); + uint32_t geckoCoordType = + (aCoordType == ATK_XY_WINDOW) + ? nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE + : nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE; + + if (Accessible* acc = GetInternalObj(ATK_OBJECT(aImage))) { + pos = acc->Position(geckoCoordType); + } + + *aAccX = pos.x; + *aAccY = pos.y; +} + +static const gchar* getImageDescriptionCB(AtkImage* aImage) { + return getDescriptionCB(ATK_OBJECT(aImage)); +} + +static void getImageSizeCB(AtkImage* aImage, gint* aAccWidth, + gint* aAccHeight) { + LayoutDeviceIntSize size(-1, -1); + if (Accessible* acc = GetInternalObj(ATK_OBJECT(aImage))) { + size = acc->Size(); + } + + *aAccWidth = size.width; + *aAccHeight = size.height; +} + +} // extern "C" + +void imageInterfaceInitCB(AtkImageIface* aIface) { + NS_ASSERTION(aIface, "no interface!"); + if (MOZ_UNLIKELY(!aIface)) return; + + aIface->get_image_position = getImagePositionCB; + aIface->get_image_description = getImageDescriptionCB; + aIface->get_image_size = getImageSizeCB; +} diff --git a/accessible/atk/nsMaiInterfaceSelection.cpp b/accessible/atk/nsMaiInterfaceSelection.cpp new file mode 100644 index 0000000000..80b4d260f1 --- /dev/null +++ b/accessible/atk/nsMaiInterfaceSelection.cpp @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InterfaceInitFuncs.h" + +#include "LocalAccessible-inl.h" +#include "AccessibleWrap.h" +#include "nsMai.h" +#include "mozilla/Likely.h" + +#include <atk/atkobject.h> +#include <atk/atkselection.h> + +using namespace mozilla::a11y; + +extern "C" { + +static gboolean addSelectionCB(AtkSelection* aSelection, gint i) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aSelection)); + if (acc && acc->IsSelect()) { + return acc->AddItemToSelection(i); + } + + return FALSE; +} + +static gboolean clearSelectionCB(AtkSelection* aSelection) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aSelection)); + if (acc && acc->IsSelect()) { + return acc->UnselectAll(); + } + + return FALSE; +} + +static AtkObject* refSelectionCB(AtkSelection* aSelection, gint i) { + AtkObject* atkObj = nullptr; + Accessible* acc = GetInternalObj(ATK_OBJECT(aSelection)); + Accessible* selectedItem = acc->GetSelectedItem(i); + if (selectedItem) { + atkObj = GetWrapperFor(selectedItem); + } + + if (atkObj) { + g_object_ref(atkObj); + } + + return atkObj; +} + +static gint getSelectionCountCB(AtkSelection* aSelection) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aSelection)); + if (acc && acc->IsSelect()) { + return acc->SelectedItemCount(); + } + + return -1; +} + +static gboolean isChildSelectedCB(AtkSelection* aSelection, gint i) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aSelection)); + if (acc && acc->IsSelect()) { + return acc->IsItemSelected(i); + } + + return FALSE; +} + +static gboolean removeSelectionCB(AtkSelection* aSelection, gint i) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aSelection)); + if (acc && acc->IsSelect()) { + return acc->RemoveItemFromSelection(i); + } + + return FALSE; +} + +static gboolean selectAllSelectionCB(AtkSelection* aSelection) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aSelection)); + if (acc && acc->IsSelect()) { + return acc->SelectAll(); + } + + return FALSE; +} +} + +void selectionInterfaceInitCB(AtkSelectionIface* aIface) { + NS_ASSERTION(aIface, "Invalid aIface"); + if (MOZ_UNLIKELY(!aIface)) return; + + aIface->add_selection = addSelectionCB; + aIface->clear_selection = clearSelectionCB; + aIface->ref_selection = refSelectionCB; + aIface->get_selection_count = getSelectionCountCB; + aIface->is_child_selected = isChildSelectedCB; + aIface->remove_selection = removeSelectionCB; + aIface->select_all_selection = selectAllSelectionCB; +} diff --git a/accessible/atk/nsMaiInterfaceTable.cpp b/accessible/atk/nsMaiInterfaceTable.cpp new file mode 100644 index 0000000000..cfba9e78d1 --- /dev/null +++ b/accessible/atk/nsMaiInterfaceTable.cpp @@ -0,0 +1,264 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InterfaceInitFuncs.h" + +#include "AccessibleWrap.h" +#include "mozilla/a11y/TableAccessible.h" +#include "nsAccessibilityService.h" +#include "nsMai.h" +#include "RemoteAccessible.h" +#include "nsTArray.h" + +#include "mozilla/Likely.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +extern "C" { +static AtkObject* refAtCB(AtkTable* aTable, gint aRowIdx, gint aColIdx) { + if (aRowIdx < 0 || aColIdx < 0) { + return nullptr; + } + + AtkObject* cellAtkObj = nullptr; + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return nullptr; + } + Accessible* cell = acc->AsTable()->CellAt(aRowIdx, aColIdx); + if (!cell) { + return nullptr; + } + + cellAtkObj = GetWrapperFor(cell); + + if (cellAtkObj) { + g_object_ref(cellAtkObj); + } + + return cellAtkObj; +} + +static gint getIndexAtCB(AtkTable* aTable, gint aRowIdx, gint aColIdx) { + if (aRowIdx < 0 || aColIdx < 0) { + return -1; + } + + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return -1; + } + return static_cast<gint>(acc->AsTable()->CellIndexAt(aRowIdx, aColIdx)); +} + +static gint getColumnAtIndexCB(AtkTable* aTable, gint aIdx) { + if (aIdx < 0) { + return -1; + } + + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return -1; + } + return static_cast<gint>(acc->AsTable()->ColIndexAt(aIdx)); +} + +static gint getRowAtIndexCB(AtkTable* aTable, gint aIdx) { + if (aIdx < 0) { + return -1; + } + + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return -1; + } + return static_cast<gint>(acc->AsTable()->RowIndexAt(aIdx)); +} + +static gint getColumnCountCB(AtkTable* aTable) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return -1; + } + return static_cast<gint>(acc->AsTable()->ColCount()); +} + +static gint getRowCountCB(AtkTable* aTable) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return -1; + } + return static_cast<gint>(acc->AsTable()->RowCount()); +} + +static gint getColumnExtentAtCB(AtkTable* aTable, gint aRowIdx, gint aColIdx) { + if (aRowIdx < 0 || aColIdx < 0) { + return -1; + } + + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return -1; + } + return static_cast<gint>(acc->AsTable()->ColExtentAt(aRowIdx, aColIdx)); +} + +static gint getRowExtentAtCB(AtkTable* aTable, gint aRowIdx, gint aColIdx) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return -1; + } + return static_cast<gint>(acc->AsTable()->RowExtentAt(aRowIdx, aColIdx)); +} + +static AtkObject* getCaptionCB(AtkTable* aTable) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return nullptr; + } + Accessible* caption = acc->AsTable()->Caption(); + return caption ? GetWrapperFor(caption) : nullptr; +} + +static const gchar* getColumnDescriptionCB(AtkTable* aTable, gint aColumn) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return nullptr; + } + nsAutoString autoStr; + acc->AsTable()->ColDescription(aColumn, autoStr); + return AccessibleWrap::ReturnString(autoStr); +} + +static AtkObject* getColumnHeaderCB(AtkTable* aTable, gint aColIdx) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return nullptr; + } + Accessible* header = AccessibleWrap::GetColumnHeader(acc->AsTable(), aColIdx); + return header ? GetWrapperFor(header) : nullptr; +} + +static const gchar* getRowDescriptionCB(AtkTable* aTable, gint aRow) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return nullptr; + } + nsAutoString autoStr; + acc->AsTable()->RowDescription(aRow, autoStr); + return AccessibleWrap::ReturnString(autoStr); +} + +static AtkObject* getRowHeaderCB(AtkTable* aTable, gint aRowIdx) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return nullptr; + } + Accessible* header = AccessibleWrap::GetRowHeader(acc->AsTable(), aRowIdx); + return header ? GetWrapperFor(header) : nullptr; +} + +static AtkObject* getSummaryCB(AtkTable* aTable) { + // Neither html:table nor xul:tree nor ARIA grid/tree have an ability to + // link an accessible object to specify a summary. There is closes method + // in TableAccessible::summary to get a summary as a string which is not + // mapped directly to ATK. + return nullptr; +} + +static gint getSelectedColumnsCB(AtkTable* aTable, gint** aSelected) { + *aSelected = nullptr; + + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return 0; + } + AutoTArray<uint32_t, 10> cols; + acc->AsTable()->SelectedColIndices(&cols); + + if (cols.IsEmpty()) return 0; + + gint* atkColumns = g_new(gint, cols.Length()); + if (!atkColumns) { + NS_WARNING("OUT OF MEMORY"); + return 0; + } + + memcpy(atkColumns, cols.Elements(), cols.Length() * sizeof(uint32_t)); + *aSelected = atkColumns; + return cols.Length(); +} + +static gint getSelectedRowsCB(AtkTable* aTable, gint** aSelected) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return 0; + } + AutoTArray<uint32_t, 10> rows; + acc->AsTable()->SelectedRowIndices(&rows); + + gint* atkRows = g_new(gint, rows.Length()); + if (!atkRows) { + NS_WARNING("OUT OF MEMORY"); + return 0; + } + + memcpy(atkRows, rows.Elements(), rows.Length() * sizeof(uint32_t)); + *aSelected = atkRows; + return rows.Length(); +} + +static gboolean isColumnSelectedCB(AtkTable* aTable, gint aColIdx) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return FALSE; + } + return static_cast<gboolean>(acc->AsTable()->IsColSelected(aColIdx)); +} + +static gboolean isRowSelectedCB(AtkTable* aTable, gint aRowIdx) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return FALSE; + } + return static_cast<gboolean>(acc->AsTable()->IsRowSelected(aRowIdx)); +} + +static gboolean isCellSelectedCB(AtkTable* aTable, gint aRowIdx, gint aColIdx) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aTable)); + if (!acc) { + return FALSE; + } + return static_cast<gboolean>( + acc->AsTable()->IsCellSelected(aRowIdx, aColIdx)); +} +} + +void tableInterfaceInitCB(AtkTableIface* aIface) { + NS_ASSERTION(aIface, "no interface!"); + if (MOZ_UNLIKELY(!aIface)) return; + + aIface->ref_at = refAtCB; + aIface->get_index_at = getIndexAtCB; + aIface->get_column_at_index = getColumnAtIndexCB; + aIface->get_row_at_index = getRowAtIndexCB; + aIface->get_n_columns = getColumnCountCB; + aIface->get_n_rows = getRowCountCB; + aIface->get_column_extent_at = getColumnExtentAtCB; + aIface->get_row_extent_at = getRowExtentAtCB; + aIface->get_caption = getCaptionCB; + aIface->get_column_description = getColumnDescriptionCB; + aIface->get_column_header = getColumnHeaderCB; + aIface->get_row_description = getRowDescriptionCB; + aIface->get_row_header = getRowHeaderCB; + aIface->get_summary = getSummaryCB; + aIface->get_selected_columns = getSelectedColumnsCB; + aIface->get_selected_rows = getSelectedRowsCB; + aIface->is_column_selected = isColumnSelectedCB; + aIface->is_row_selected = isRowSelectedCB; + aIface->is_selected = isCellSelectedCB; +} diff --git a/accessible/atk/nsMaiInterfaceTableCell.cpp b/accessible/atk/nsMaiInterfaceTableCell.cpp new file mode 100644 index 0000000000..6d61344051 --- /dev/null +++ b/accessible/atk/nsMaiInterfaceTableCell.cpp @@ -0,0 +1,148 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InterfaceInitFuncs.h" + +#include "mozilla/a11y/TableAccessible.h" +#include "mozilla/a11y/TableCellAccessible.h" +#include "nsAccessibilityService.h" +#include "nsMai.h" +#include "RemoteAccessible.h" +#include "nsTArray.h" + +#include "mozilla/Likely.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +extern "C" { +static gint GetColumnSpanCB(AtkTableCell* aCell) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aCell)); + if (!acc) { + return 0; + } + return static_cast<gint>(acc->AsTableCell()->ColExtent()); +} + +static gint GetRowSpanCB(AtkTableCell* aCell) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aCell)); + if (!acc) { + return 0; + } + return static_cast<gint>(acc->AsTableCell()->RowExtent()); +} + +static gboolean GetPositionCB(AtkTableCell* aCell, gint* aRow, gint* aCol) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aCell)); + if (!acc) { + return false; + } + TableCellAccessible* cell = acc->AsTableCell(); + if (!cell) { + return false; + } + *aRow = static_cast<gint>(cell->RowIdx()); + *aCol = static_cast<gint>(cell->ColIdx()); + return true; +} + +static gboolean GetRowColumnSpanCB(AtkTableCell* aCell, gint* aRow, gint* aCol, + gint* aRowExtent, gint* aColExtent) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aCell)); + if (!acc) { + return false; + } + TableCellAccessible* cellAcc = acc->AsTableCell(); + if (!cellAcc) { + return false; + } + *aCol = static_cast<gint>(cellAcc->ColIdx()); + *aRow = static_cast<gint>(cellAcc->RowIdx()); + *aColExtent = static_cast<gint>(cellAcc->ColExtent()); + *aRowExtent = static_cast<gint>(cellAcc->RowExtent()); + return true; +} + +static AtkObject* GetTableCB(AtkTableCell* aTableCell) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aTableCell)); + if (!acc) { + return nullptr; + } + TableCellAccessible* cell = acc->AsTableCell(); + if (!cell) { + return nullptr; + } + TableAccessible* table = cell->Table(); + if (!table) { + return nullptr; + } + Accessible* tableAcc = table->AsAccessible(); + return tableAcc ? GetWrapperFor(tableAcc) : nullptr; +} + +static GPtrArray* GetColumnHeaderCellsCB(AtkTableCell* aCell) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aCell)); + if (!acc) { + return nullptr; + } + TableCellAccessible* cell = acc->AsTableCell(); + if (!cell) { + return nullptr; + } + AutoTArray<Accessible*, 10> headers; + cell->ColHeaderCells(&headers); + if (headers.IsEmpty()) { + return nullptr; + } + + GPtrArray* atkHeaders = g_ptr_array_sized_new(headers.Length()); + for (Accessible* header : headers) { + AtkObject* atkHeader = GetWrapperFor(header); + g_object_ref(atkHeader); + g_ptr_array_add(atkHeaders, atkHeader); + } + + return atkHeaders; +} + +static GPtrArray* GetRowHeaderCellsCB(AtkTableCell* aCell) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aCell)); + if (!acc) { + return nullptr; + } + TableCellAccessible* cell = acc->AsTableCell(); + if (!cell) { + return nullptr; + } + AutoTArray<Accessible*, 10> headers; + cell->RowHeaderCells(&headers); + if (headers.IsEmpty()) { + return nullptr; + } + + GPtrArray* atkHeaders = g_ptr_array_sized_new(headers.Length()); + for (Accessible* header : headers) { + AtkObject* atkHeader = GetWrapperFor(header); + g_object_ref(atkHeader); + g_ptr_array_add(atkHeaders, atkHeader); + } + + return atkHeaders; +} +} + +void tableCellInterfaceInitCB(AtkTableCellIface* aIface) { + NS_ASSERTION(aIface, "no interface!"); + if (MOZ_UNLIKELY(!aIface)) return; + + aIface->get_column_span = GetColumnSpanCB; + aIface->get_column_header_cells = GetColumnHeaderCellsCB; + aIface->get_position = GetPositionCB; + aIface->get_row_span = GetRowSpanCB; + aIface->get_row_header_cells = GetRowHeaderCellsCB; + aIface->get_row_column_span = GetRowColumnSpanCB; + aIface->get_table = GetTableCB; +} diff --git a/accessible/atk/nsMaiInterfaceText.cpp b/accessible/atk/nsMaiInterfaceText.cpp new file mode 100644 index 0000000000..b5c0dcc38d --- /dev/null +++ b/accessible/atk/nsMaiInterfaceText.cpp @@ -0,0 +1,557 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InterfaceInitFuncs.h" +#include "mozilla/a11y/PDocAccessible.h" +#include "nsAccessibilityService.h" +#include "LocalAccessible-inl.h" +#include "HyperTextAccessible-inl.h" +#include "nsMai.h" +#include "RemoteAccessible.h" +#include "AccAttributes.h" + +#include "nsIAccessibleTypes.h" +#include "nsISimpleEnumerator.h" +#include "nsUTF8Utils.h" + +#include "mozilla/Likely.h" + +#include "DOMtoATK.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +static const char* sAtkTextAttrNames[ATK_TEXT_ATTR_LAST_DEFINED]; + +static AtkAttributeSet* ConvertToAtkTextAttributeSet( + AccAttributes* aAttributes) { + if (!aAttributes) { + // This can happen if an Accessible dies in the content process, but the + // parent hasn't been udpated yet. + return nullptr; + } + + AtkAttributeSet* atkAttributeSet = nullptr; + + for (auto iter : *aAttributes) { + AtkAttribute* atkAttr = (AtkAttribute*)g_malloc(sizeof(AtkAttribute)); + nsAutoString value; + // We set atkAttr->name directly for each case. For the value, we set the + // value string for each case. atkAttr->value is set at the end based on the + // value string. + + // Set atkAttr->name to a specific ATK attribute name. + auto atkName = [&atkAttr](AtkTextAttribute aAttrNum) { + atkAttr->name = g_strdup(sAtkTextAttrNames[aAttrNum]); + }; + // Set value to a formatted ATK color value. + auto colorValue = [&iter, &value] { + // The format of the atk attribute is r,g,b and the gecko one is + // rgb(r, g, b). + auto color = iter.Value<Color>(); + MOZ_ASSERT(color); + value.AppendInt(NS_GET_R(color->mValue)); + value.Append(','); + value.AppendInt(NS_GET_G(color->mValue)); + value.Append(','); + value.AppendInt(NS_GET_B(color->mValue)); + }; + + nsAtom* name = iter.Name(); + if (name == nsGkAtoms::color) { + atkName(ATK_TEXT_ATTR_FG_COLOR); + colorValue(); + } else if (name == nsGkAtoms::backgroundColor) { + atkName(ATK_TEXT_ATTR_BG_COLOR); + colorValue(); + } else if (name == nsGkAtoms::font_family) { + atkName(ATK_TEXT_ATTR_FAMILY_NAME); + iter.ValueAsString(value); + } else if (name == nsGkAtoms::font_size) { + atkName(ATK_TEXT_ATTR_SIZE); + // ATK wants the number of points without pt at the end. + auto fontSize = iter.Value<FontSize>(); + MOZ_ASSERT(fontSize); + value.AppendInt(fontSize->mValue); + } else if (name == nsGkAtoms::fontWeight) { + atkName(ATK_TEXT_ATTR_WEIGHT); + iter.ValueAsString(value); + } else if (name == nsGkAtoms::invalid) { + atkName(ATK_TEXT_ATTR_INVALID); + iter.ValueAsString(value); + } else { + nsAutoString nameStr; + iter.NameAsString(nameStr); + atkAttr->name = g_strdup(NS_ConvertUTF16toUTF8(nameStr).get()); + iter.ValueAsString(value); + } + + atkAttr->value = g_strdup(NS_ConvertUTF16toUTF8(value).get()); + atkAttributeSet = g_slist_prepend(atkAttributeSet, atkAttr); + } + + // libatk-adaptor will free it + return atkAttributeSet; +} + +extern "C" { + +static gchar* getTextCB(AtkText* aText, gint aStartOffset, gint aEndOffset) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc || !acc->IsTextRole()) { + return nullptr; + } + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (!text) { + return nullptr; + } + return DOMtoATK::NewATKString(text, aStartOffset, aEndOffset); +} + +static gint getCharacterCountCB(AtkText* aText); + +// Note: this does not support magic offsets, which is fine for its callers +// which do not implement any. +static gchar* getCharTextAtOffset(AtkText* aText, gint aOffset, + gint* aStartOffset, gint* aEndOffset) { + gint end = aOffset + 1; + gint count = getCharacterCountCB(aText); + + if (aOffset > count) { + aOffset = count; + } + if (end > count) { + end = count; + } + if (aOffset < 0) { + aOffset = 0; + } + if (end < 0) { + end = 0; + } + *aStartOffset = aOffset; + *aEndOffset = end; + + return getTextCB(aText, aOffset, end); +} + +static gchar* getTextAfterOffsetCB(AtkText* aText, gint aOffset, + AtkTextBoundary aBoundaryType, + gint* aStartOffset, gint* aEndOffset) { + if (aBoundaryType == ATK_TEXT_BOUNDARY_CHAR) { + return getCharTextAtOffset(aText, aOffset + 1, aStartOffset, aEndOffset); + } + + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc) { + return nullptr; + } + + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (!text || !acc->IsTextRole()) { + return nullptr; + } + + nsAutoString autoStr; + int32_t startOffset = 0, endOffset = 0; + text->TextAfterOffset(aOffset, aBoundaryType, &startOffset, &endOffset, + autoStr); + + *aStartOffset = startOffset; + *aEndOffset = endOffset; + + // libspi will free it. + return DOMtoATK::Convert(autoStr); +} + +static gchar* getTextAtOffsetCB(AtkText* aText, gint aOffset, + AtkTextBoundary aBoundaryType, + gint* aStartOffset, gint* aEndOffset) { + if (aBoundaryType == ATK_TEXT_BOUNDARY_CHAR) { + return getCharTextAtOffset(aText, aOffset, aStartOffset, aEndOffset); + } + + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc) { + return nullptr; + } + + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (!text || !acc->IsTextRole()) { + return nullptr; + } + + nsAutoString autoStr; + int32_t startOffset = 0, endOffset = 0; + text->TextAtOffset(aOffset, aBoundaryType, &startOffset, &endOffset, autoStr); + + *aStartOffset = startOffset; + *aEndOffset = endOffset; + + // libspi will free it. + return DOMtoATK::Convert(autoStr); +} + +static gunichar getCharacterAtOffsetCB(AtkText* aText, gint aOffset) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc) { + return 0; + } + + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (text) { + return DOMtoATK::ATKCharacter(text, aOffset); + } + + return 0; +} + +static gchar* getTextBeforeOffsetCB(AtkText* aText, gint aOffset, + AtkTextBoundary aBoundaryType, + gint* aStartOffset, gint* aEndOffset) { + if (aBoundaryType == ATK_TEXT_BOUNDARY_CHAR) { + return getCharTextAtOffset(aText, aOffset - 1, aStartOffset, aEndOffset); + } + + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc) { + return nullptr; + } + + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (!text || !acc->IsTextRole()) { + return nullptr; + } + + nsAutoString autoStr; + int32_t startOffset = 0, endOffset = 0; + text->TextBeforeOffset(aOffset, aBoundaryType, &startOffset, &endOffset, + autoStr); + + *aStartOffset = startOffset; + *aEndOffset = endOffset; + + // libspi will free it. + return DOMtoATK::Convert(autoStr); +} + +static gint getCaretOffsetCB(AtkText* aText) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc) { + return -1; + } + + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (!text || !acc->IsTextRole()) { + return -1; + } + + return static_cast<gint>(text->CaretOffset()); +} + +static AtkAttributeSet* getRunAttributesCB(AtkText* aText, gint aOffset, + gint* aStartOffset, + gint* aEndOffset) { + *aStartOffset = -1; + *aEndOffset = -1; + int32_t startOffset = 0, endOffset = 0; + + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc) { + return nullptr; + } + + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (!text || !acc->IsTextRole()) { + return nullptr; + } + + RefPtr<AccAttributes> attributes = + text->TextAttributes(false, aOffset, &startOffset, &endOffset); + + *aStartOffset = startOffset; + *aEndOffset = endOffset; + + return ConvertToAtkTextAttributeSet(attributes); +} + +static AtkAttributeSet* getDefaultAttributesCB(AtkText* aText) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc) { + return nullptr; + } + + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (!text || !acc->IsTextRole()) { + return nullptr; + } + + RefPtr<AccAttributes> attributes = text->DefaultTextAttributes(); + return ConvertToAtkTextAttributeSet(attributes); +} + +static void getCharacterExtentsCB(AtkText* aText, gint aOffset, gint* aX, + gint* aY, gint* aWidth, gint* aHeight, + AtkCoordType aCoords) { + if (!aX || !aY || !aWidth || !aHeight) { + return; + } + *aX = *aY = *aWidth = *aHeight = -1; + + uint32_t geckoCoordType; + if (aCoords == ATK_XY_SCREEN) { + geckoCoordType = nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE; + } else { + geckoCoordType = nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE; + } + + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc) { + return; + } + + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (!text || !acc->IsTextRole()) { + return; + } + + LayoutDeviceIntRect rect = text->CharBounds(aOffset, geckoCoordType); + + *aX = rect.x; + *aY = rect.y; + *aWidth = rect.width; + *aHeight = rect.height; +} + +static void getRangeExtentsCB(AtkText* aText, gint aStartOffset, + gint aEndOffset, AtkCoordType aCoords, + AtkTextRectangle* aRect) { + if (!aRect) { + return; + } + aRect->x = aRect->y = aRect->width = aRect->height = -1; + + uint32_t geckoCoordType; + if (aCoords == ATK_XY_SCREEN) { + geckoCoordType = nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE; + } else { + geckoCoordType = nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE; + } + + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc) { + return; + } + + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (!text || !acc->IsTextRole()) { + return; + } + + LayoutDeviceIntRect rect = + text->TextBounds(aStartOffset, aEndOffset, geckoCoordType); + + aRect->x = rect.x; + aRect->y = rect.y; + aRect->width = rect.width; + aRect->height = rect.height; +} + +static gint getCharacterCountCB(AtkText* aText) { + if (Accessible* acc = GetInternalObj(ATK_OBJECT(aText))) { + if (HyperTextAccessibleBase* text = acc->AsHyperTextBase()) { + return static_cast<gint>(text->CharacterCount()); + } + } + return 0; +} + +static gint getOffsetAtPointCB(AtkText* aText, gint aX, gint aY, + AtkCoordType aCoords) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc) { + return -1; + } + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (!text || !acc->IsTextRole()) { + return -1; + } + return static_cast<gint>(text->OffsetAtPoint( + aX, aY, + (aCoords == ATK_XY_SCREEN + ? nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE + : nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE))); +} + +static gint getTextSelectionCountCB(AtkText* aText) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc) { + return 0; + } + + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (!text || !acc->IsTextRole()) { + return 0; + } + + return text->SelectionCount(); +} + +static gchar* getTextSelectionCB(AtkText* aText, gint aSelectionNum, + gint* aStartOffset, gint* aEndOffset) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc) { + return nullptr; + } + + int32_t startOffset = 0, endOffset = 0; + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (!text || !acc->IsTextRole()) { + return nullptr; + } + + text->SelectionBoundsAt(aSelectionNum, &startOffset, &endOffset); + *aStartOffset = startOffset; + *aEndOffset = endOffset; + + return getTextCB(aText, *aStartOffset, *aEndOffset); +} + +// set methods +static gboolean addTextSelectionCB(AtkText* aText, gint aStartOffset, + gint aEndOffset) { + AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText)); + if (accWrap) { + HyperTextAccessible* text = accWrap->AsHyperText(); + if (!text || !text->IsTextRole()) { + return FALSE; + } + + return text->AddToSelection(aStartOffset, aEndOffset); + } + if (RemoteAccessible* proxy = GetProxy(ATK_OBJECT(aText))) { + return proxy->AddToSelection(aStartOffset, aEndOffset); + } + + return FALSE; +} + +static gboolean removeTextSelectionCB(AtkText* aText, gint aSelectionNum) { + AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText)); + if (accWrap) { + HyperTextAccessible* text = accWrap->AsHyperText(); + if (!text || !text->IsTextRole()) { + return FALSE; + } + + return text->RemoveFromSelection(aSelectionNum); + } + if (RemoteAccessible* proxy = GetProxy(ATK_OBJECT(aText))) { + return proxy->RemoveFromSelection(aSelectionNum); + } + + return FALSE; +} + +static gboolean setTextSelectionCB(AtkText* aText, gint aSelectionNum, + gint aStartOffset, gint aEndOffset) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc || !acc->IsTextRole()) { + return FALSE; + } + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (!text) { + return FALSE; + } + return text->SetSelectionBoundsAt(aSelectionNum, aStartOffset, aEndOffset); +} + +static gboolean setCaretOffsetCB(AtkText* aText, gint aOffset) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc) { + return FALSE; + } + + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (!text || !acc->IsTextRole()) { + return FALSE; + } + + text->SetCaretOffset(aOffset); + return TRUE; +} + +static gboolean scrollSubstringToCB(AtkText* aText, gint aStartOffset, + gint aEndOffset, AtkScrollType aType) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc) { + return FALSE; + } + + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (!text) { + return FALSE; + } + + text->ScrollSubstringTo(aStartOffset, aEndOffset, aType); + + return TRUE; +} + +static gboolean scrollSubstringToPointCB(AtkText* aText, gint aStartOffset, + gint aEndOffset, AtkCoordType aCoords, + gint aX, gint aY) { + Accessible* acc = GetInternalObj(ATK_OBJECT(aText)); + if (!acc) { + return FALSE; + } + + HyperTextAccessibleBase* text = acc->AsHyperTextBase(); + if (!text) { + return FALSE; + } + + text->ScrollSubstringToPoint(aStartOffset, aEndOffset, aCoords, aX, aY); + return TRUE; +} +} + +void textInterfaceInitCB(AtkTextIface* aIface) { + NS_ASSERTION(aIface, "Invalid aIface"); + if (MOZ_UNLIKELY(!aIface)) return; + + aIface->get_text = getTextCB; + aIface->get_text_after_offset = getTextAfterOffsetCB; + aIface->get_text_at_offset = getTextAtOffsetCB; + aIface->get_character_at_offset = getCharacterAtOffsetCB; + aIface->get_text_before_offset = getTextBeforeOffsetCB; + aIface->get_caret_offset = getCaretOffsetCB; + aIface->get_run_attributes = getRunAttributesCB; + aIface->get_default_attributes = getDefaultAttributesCB; + aIface->get_character_extents = getCharacterExtentsCB; + aIface->get_range_extents = getRangeExtentsCB; + aIface->get_character_count = getCharacterCountCB; + aIface->get_offset_at_point = getOffsetAtPointCB; + aIface->get_n_selections = getTextSelectionCountCB; + aIface->get_selection = getTextSelectionCB; + + // set methods + aIface->add_selection = addTextSelectionCB; + aIface->remove_selection = removeTextSelectionCB; + aIface->set_selection = setTextSelectionCB; + aIface->set_caret_offset = setCaretOffsetCB; + + if (IsAtkVersionAtLeast(2, 32)) { + aIface->scroll_substring_to = scrollSubstringToCB; + aIface->scroll_substring_to_point = scrollSubstringToPointCB; + } + + // Cache the string values of the atk text attribute names. + for (uint32_t i = 0; i < ArrayLength(sAtkTextAttrNames); i++) { + sAtkTextAttrNames[i] = + atk_text_attribute_get_name(static_cast<AtkTextAttribute>(i)); + } +} diff --git a/accessible/atk/nsMaiInterfaceValue.cpp b/accessible/atk/nsMaiInterfaceValue.cpp new file mode 100644 index 0000000000..05a7da171e --- /dev/null +++ b/accessible/atk/nsMaiInterfaceValue.cpp @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InterfaceInitFuncs.h" + +#include "AccessibleWrap.h" +#include "nsMai.h" +#include "RemoteAccessible.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Likely.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +extern "C" { + +static void getCurrentValueCB(AtkValue* obj, GValue* value) { + Accessible* acc = GetInternalObj(ATK_OBJECT(obj)); + if (!acc) { + return; + } + + memset(value, 0, sizeof(GValue)); + double accValue = acc->CurValue(); + if (std::isnan(accValue)) return; + + g_value_init(value, G_TYPE_DOUBLE); + g_value_set_double(value, accValue); +} + +static void getMaximumValueCB(AtkValue* obj, GValue* value) { + Accessible* acc = GetInternalObj(ATK_OBJECT(obj)); + if (!acc) { + return; + } + + memset(value, 0, sizeof(GValue)); + double accValue = acc->MaxValue(); + if (std::isnan(accValue)) return; + + g_value_init(value, G_TYPE_DOUBLE); + g_value_set_double(value, accValue); +} + +static void getMinimumValueCB(AtkValue* obj, GValue* value) { + Accessible* acc = GetInternalObj(ATK_OBJECT(obj)); + if (!acc) { + return; + } + + memset(value, 0, sizeof(GValue)); + double accValue = acc->MinValue(); + if (std::isnan(accValue)) return; + + g_value_init(value, G_TYPE_DOUBLE); + g_value_set_double(value, accValue); +} + +static void getMinimumIncrementCB(AtkValue* obj, GValue* minimumIncrement) { + Accessible* acc = GetInternalObj(ATK_OBJECT(obj)); + if (!acc) { + return; + } + + memset(minimumIncrement, 0, sizeof(GValue)); + double accValue = acc->Step(); + if (std::isnan(accValue)) { + accValue = 0; // zero if the minimum increment is undefined + } + + g_value_init(minimumIncrement, G_TYPE_DOUBLE); + g_value_set_double(minimumIncrement, accValue); +} + +static gboolean setCurrentValueCB(AtkValue* obj, const GValue* value) { + Accessible* acc = GetInternalObj(ATK_OBJECT(obj)); + if (!acc) { + return false; + } + + double accValue = g_value_get_double(value); + return acc->SetCurValue(accValue); +} + +void valueInterfaceInitCB(AtkValueIface* aIface) { + NS_ASSERTION(aIface, "Invalid aIface"); + if (MOZ_UNLIKELY(!aIface)) return; + + aIface->get_current_value = getCurrentValueCB; + aIface->get_maximum_value = getMaximumValueCB; + aIface->get_minimum_value = getMinimumValueCB; + aIface->get_minimum_increment = getMinimumIncrementCB; + aIface->set_current_value = setCurrentValueCB; +} +} diff --git a/accessible/atk/nsStateMap.h b/accessible/atk/nsStateMap.h new file mode 100644 index 0000000000..3587ccd6cf --- /dev/null +++ b/accessible/atk/nsStateMap.h @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <atk/atk.h> +#include "AccessibleWrap.h" + +#include <type_traits> + +// clang-format off +/****************************************************************************** +The following accessible states aren't translated, just ignored: + STATE_READONLY: Supported indirectly via EXT_STATE_EDITABLE + STATE_HOTTRACKED: No ATK equivalent. No known use case. + The nsIAccessible state is not currently supported. + STATE_FLOATING: No ATK equivalent. No known use case. + The nsIAccessible state is not currently supported. + STATE_MOVEABLE: No ATK equivalent. No known use case. + The nsIAccessible state is not currently supported. + STATE_SELFVOICING: No ATK equivalent -- the object has self-TTS. + The nsIAccessible state is not currently supported. + STATE_LINKED: The object is formatted as a hyperlink. Supported via ATK_ROLE_LINK. + STATE_EXTSELECTABLE: Indicates that an object extends its selection. + This is supported via STATE_MULTISELECTABLE. + STATE_PROTECTED: The object is a password-protected edit control. + Supported via ATK_ROLE_PASSWORD_TEXT + STATE_PINNED: The object is pinned, usually indicating it is fixed in + place and has permanence. No ATK equivalent. The + accessible state is not currently supported. + +The following ATK states are not supported: + ATK_STATE_ARMED: No clear use case, used briefly when button is activated + ATK_STATE_HAS_TOOLTIP: No clear use case, no IA2 equivalent + ATK_STATE_ICONIFIED: Mozilla does not have elements which are collapsable into icons + ATK_STATE_TRUNCATED: No clear use case. Indicates that an object's onscreen content is truncated, + e.g. a text value in a spreadsheet cell. No IA2 state. +******************************************************************************/ +// clang-format on + +enum EStateMapEntryType { + kMapDirectly, + kMapOpposite, // For example, UNAVAILABLE is the opposite of ENABLED + kNoStateChange, // Don't fire state change event +}; + +const AtkStateType kNone = ATK_STATE_INVALID; + +struct AtkStateMap { + AtkStateType atkState; + EStateMapEntryType stateMapEntryType; +}; + +// Map array from cross platform states to ATK states +static const AtkStateMap gAtkStateMap[] = + { + // Cross Platform States + // clang-format off + { kNone, kMapOpposite }, // states::UNAVAILABLE = 1 << 0 + { ATK_STATE_SELECTED, kMapDirectly }, // states::SELECTED = 1 << 1 + { ATK_STATE_FOCUSED, kMapDirectly }, // states::FOCUSED = 1 << 2 + { ATK_STATE_PRESSED, kMapDirectly }, // states::PRESSED = 1 << 3 + { ATK_STATE_CHECKED, kMapDirectly }, // states::CHECKED = 1 << 4 + { ATK_STATE_INDETERMINATE, kMapDirectly }, // states::MIXED = 1 << 5 + { kNone, kMapDirectly }, // states::READONLY = 1 << 6 + { kNone, kMapDirectly }, // states::HOTTRACKED = 1 << 7 + { ATK_STATE_DEFAULT, kMapDirectly }, // states::DEFAULT = 1 << 8 + { ATK_STATE_EXPANDED, kMapDirectly }, // states::EXPANDED = 1 << 9 + { kNone, kNoStateChange }, // states::COLLAPSED = 1 << 10 + { ATK_STATE_BUSY, kMapDirectly }, // states::BUSY = 1 << 11 + { kNone, kMapDirectly }, // states::FLOATING = 1 << 12 + { ATK_STATE_CHECKABLE, kMapDirectly }, // states::CHECKABLE = 1 << 13 + { ATK_STATE_ANIMATED, kMapDirectly }, // states::ANIMATED = 1 << 14 + { ATK_STATE_VISIBLE, kMapOpposite }, // states::INVISIBLE = 1 << 15 + { ATK_STATE_SHOWING, kMapOpposite }, // states::OFFSCREEN = 1 << 16 + { ATK_STATE_RESIZABLE, kMapDirectly }, // states::SIZEABLE = 1 << 17 + { kNone, kMapDirectly }, // states::MOVEABLE = 1 << 18 + { kNone, kMapDirectly }, // states::SELFVOICING = 1 << 19 + { ATK_STATE_FOCUSABLE, kMapDirectly }, // states::FOCUSABLE = 1 << 20 + { ATK_STATE_SELECTABLE, kMapDirectly }, // states::SELECTABLE = 1 << 21 + { kNone, kMapDirectly }, // states::LINKED = 1 << 22 + { ATK_STATE_VISITED, kMapDirectly }, // states::TRAVERSED = 1 << 23 + { ATK_STATE_MULTISELECTABLE, kMapDirectly }, // states::MULTISELECTABLE = 1 << 24 + { kNone, kMapDirectly }, // states::EXTSELECTABLE = 1 << 25 + { ATK_STATE_REQUIRED, kMapDirectly }, // states::STATE_REQUIRED = 1 << 26 + { kNone, kMapDirectly }, // states::ALERT_MEDIUM = 1 << 27 + { ATK_STATE_INVALID_ENTRY, kMapDirectly }, // states::INVALID = 1 << 28 + { kNone, kMapDirectly }, // states::PROTECTED = 1 << 29 + { ATK_STATE_HAS_POPUP, kMapDirectly }, // states::HASPOPUP = 1 << 30 + { ATK_STATE_SUPPORTS_AUTOCOMPLETION, kMapDirectly }, // states::SUPPORTS_AUTOCOMPLETION = 1 << 31 + { ATK_STATE_DEFUNCT, kMapDirectly }, // states::DEFUNCT = 1 << 32 + { ATK_STATE_SELECTABLE_TEXT, kMapDirectly }, // states::SELECTABLE_TEXT = 1 << 33 + { ATK_STATE_EDITABLE, kMapDirectly }, // states::EDITABLE = 1 << 34 + { ATK_STATE_ACTIVE, kMapDirectly }, // states::ACTIVE = 1 << 35 + { ATK_STATE_MODAL, kMapDirectly }, // states::MODAL = 1 << 36 + { ATK_STATE_MULTI_LINE, kMapDirectly }, // states::MULTI_LINE = 1 << 37 + { ATK_STATE_HORIZONTAL, kMapDirectly }, // states::HORIZONTAL = 1 << 38 + { ATK_STATE_OPAQUE, kMapDirectly }, // states::OPAQUE = 1 << 39 + { ATK_STATE_SINGLE_LINE, kMapDirectly }, // states::SINGLE_LINE = 1 << 40 + { ATK_STATE_TRANSIENT, kMapDirectly }, // states::TRANSIENT = 1 << 41 + { ATK_STATE_VERTICAL, kMapDirectly }, // states::VERTICAL = 1 << 42 + { ATK_STATE_STALE, kMapDirectly }, // states::STALE = 1 << 43 + { ATK_STATE_ENABLED, kMapDirectly }, // states::ENABLED = 1 << 44 + { ATK_STATE_SENSITIVE, kMapDirectly }, // states::SENSITIVE = 1 << 45 + { ATK_STATE_EXPANDABLE, kMapDirectly }, // states::EXPANDABLE = 1 << 46 + { kNone, kMapDirectly }, // states::PINNED = 1 << 47 + { ATK_STATE_ACTIVE, kMapDirectly } // states::CURRENT = 1 << 48 + // clang-format on +}; + +static const auto gAtkStateMapLen = std::extent<decltype(gAtkStateMap)>::value; + +static_assert(((uint64_t)0x1) << (gAtkStateMapLen - 1) == + mozilla::a11y::states::LAST_ENTRY, + "ATK states map is out of sync with internal states"); |