Bug 1700051: part 36) Reduce accessibility of `SoftText::mBegin` to `private`. r...
[gecko.git] / accessible / atk / AccessibleWrap.cpp
blob4e60dab084e48a1c7c6cbdef27052a2d415370eb
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "AccessibleWrap.h"
9 #include "LocalAccessible-inl.h"
10 #include "ApplicationAccessibleWrap.h"
11 #include "InterfaceInitFuncs.h"
12 #include "nsAccUtils.h"
13 #include "mozilla/a11y/PDocAccessible.h"
14 #include "OuterDocAccessible.h"
15 #include "RemoteAccessible.h"
16 #include "DocAccessibleParent.h"
17 #include "RootAccessible.h"
18 #include "TableAccessible.h"
19 #include "TableCellAccessible.h"
20 #include "nsMai.h"
21 #include "nsMaiHyperlink.h"
22 #include "nsString.h"
23 #include "nsStateMap.h"
24 #include "mozilla/a11y/Platform.h"
25 #include "Relation.h"
26 #include "RootAccessible.h"
27 #include "States.h"
28 #include "nsISimpleEnumerator.h"
30 #include "mozilla/ArrayUtils.h"
31 #include "mozilla/Sprintf.h"
32 #include "nsComponentManagerUtils.h"
33 #include "nsIPersistentProperties2.h"
35 using namespace mozilla;
36 using namespace mozilla::a11y;
38 MaiAtkObject::EAvailableAtkSignals MaiAtkObject::gAvailableAtkSignals =
39 eUnknown;
41 // defined in ApplicationAccessibleWrap.cpp
42 extern "C" GType g_atk_hyperlink_impl_type;
44 /* MaiAtkObject */
46 enum {
47 ACTIVATE,
48 CREATE,
49 DEACTIVATE,
50 DESTROY,
51 MAXIMIZE,
52 MINIMIZE,
53 RESIZE,
54 RESTORE,
55 LAST_SIGNAL
58 enum MaiInterfaceType {
59 MAI_INTERFACE_COMPONENT, /* 0 */
60 MAI_INTERFACE_ACTION,
61 MAI_INTERFACE_VALUE,
62 MAI_INTERFACE_EDITABLE_TEXT,
63 MAI_INTERFACE_HYPERTEXT,
64 MAI_INTERFACE_HYPERLINK_IMPL,
65 MAI_INTERFACE_SELECTION,
66 MAI_INTERFACE_TABLE,
67 MAI_INTERFACE_TEXT,
68 MAI_INTERFACE_DOCUMENT,
69 MAI_INTERFACE_IMAGE, /* 10 */
70 MAI_INTERFACE_TABLE_CELL
73 static GType GetAtkTypeForMai(MaiInterfaceType type) {
74 switch (type) {
75 case MAI_INTERFACE_COMPONENT:
76 return ATK_TYPE_COMPONENT;
77 case MAI_INTERFACE_ACTION:
78 return ATK_TYPE_ACTION;
79 case MAI_INTERFACE_VALUE:
80 return ATK_TYPE_VALUE;
81 case MAI_INTERFACE_EDITABLE_TEXT:
82 return ATK_TYPE_EDITABLE_TEXT;
83 case MAI_INTERFACE_HYPERTEXT:
84 return ATK_TYPE_HYPERTEXT;
85 case MAI_INTERFACE_HYPERLINK_IMPL:
86 return g_atk_hyperlink_impl_type;
87 case MAI_INTERFACE_SELECTION:
88 return ATK_TYPE_SELECTION;
89 case MAI_INTERFACE_TABLE:
90 return ATK_TYPE_TABLE;
91 case MAI_INTERFACE_TEXT:
92 return ATK_TYPE_TEXT;
93 case MAI_INTERFACE_DOCUMENT:
94 return ATK_TYPE_DOCUMENT;
95 case MAI_INTERFACE_IMAGE:
96 return ATK_TYPE_IMAGE;
97 case MAI_INTERFACE_TABLE_CELL:
98 MOZ_ASSERT(false);
100 return G_TYPE_INVALID;
103 #define NON_USER_EVENT ":system"
105 // The atk interfaces we can expose without checking what version of ATK we are
106 // dealing with. At the moment AtkTableCell is the only interface we can't
107 // always expose.
108 static const GInterfaceInfo atk_if_infos[] = {
109 {(GInterfaceInitFunc)componentInterfaceInitCB,
110 (GInterfaceFinalizeFunc) nullptr, nullptr},
111 {(GInterfaceInitFunc)actionInterfaceInitCB,
112 (GInterfaceFinalizeFunc) nullptr, nullptr},
113 {(GInterfaceInitFunc)valueInterfaceInitCB, (GInterfaceFinalizeFunc) nullptr,
114 nullptr},
115 {(GInterfaceInitFunc)editableTextInterfaceInitCB,
116 (GInterfaceFinalizeFunc) nullptr, nullptr},
117 {(GInterfaceInitFunc)hypertextInterfaceInitCB,
118 (GInterfaceFinalizeFunc) nullptr, nullptr},
119 {(GInterfaceInitFunc)hyperlinkImplInterfaceInitCB,
120 (GInterfaceFinalizeFunc) nullptr, nullptr},
121 {(GInterfaceInitFunc)selectionInterfaceInitCB,
122 (GInterfaceFinalizeFunc) nullptr, nullptr},
123 {(GInterfaceInitFunc)tableInterfaceInitCB, (GInterfaceFinalizeFunc) nullptr,
124 nullptr},
125 {(GInterfaceInitFunc)textInterfaceInitCB, (GInterfaceFinalizeFunc) nullptr,
126 nullptr},
127 {(GInterfaceInitFunc)documentInterfaceInitCB,
128 (GInterfaceFinalizeFunc) nullptr, nullptr},
129 {(GInterfaceInitFunc)imageInterfaceInitCB, (GInterfaceFinalizeFunc) nullptr,
130 nullptr}};
132 static GQuark quark_mai_hyperlink = 0;
134 AtkHyperlink* MaiAtkObject::GetAtkHyperlink() {
135 NS_ASSERTION(quark_mai_hyperlink, "quark_mai_hyperlink not initialized");
136 MaiHyperlink* maiHyperlink =
137 (MaiHyperlink*)g_object_get_qdata(G_OBJECT(this), quark_mai_hyperlink);
138 if (!maiHyperlink) {
139 maiHyperlink = new MaiHyperlink(accWrap);
140 g_object_set_qdata(G_OBJECT(this), quark_mai_hyperlink, maiHyperlink);
143 return maiHyperlink->GetAtkHyperlink();
146 void MaiAtkObject::Shutdown() {
147 accWrap.SetBits(0);
148 MaiHyperlink* maiHyperlink =
149 (MaiHyperlink*)g_object_get_qdata(G_OBJECT(this), quark_mai_hyperlink);
150 if (maiHyperlink) {
151 delete maiHyperlink;
152 g_object_set_qdata(G_OBJECT(this), quark_mai_hyperlink, nullptr);
156 struct MaiAtkObjectClass {
157 AtkObjectClass parent_class;
160 static guint mai_atk_object_signals[LAST_SIGNAL] = {
164 static void MaybeFireNameChange(AtkObject* aAtkObj, const nsString& aNewName);
166 G_BEGIN_DECLS
167 /* callbacks for MaiAtkObject */
168 static void classInitCB(AtkObjectClass* aClass);
169 static void initializeCB(AtkObject* aAtkObj, gpointer aData);
170 static void finalizeCB(GObject* aObj);
172 /* callbacks for AtkObject virtual functions */
173 static const gchar* getNameCB(AtkObject* aAtkObj);
174 /* getDescriptionCB is also used by image interface */
175 const gchar* getDescriptionCB(AtkObject* aAtkObj);
176 static AtkRole getRoleCB(AtkObject* aAtkObj);
177 static AtkAttributeSet* getAttributesCB(AtkObject* aAtkObj);
178 static const gchar* GetLocaleCB(AtkObject*);
179 static AtkObject* getParentCB(AtkObject* aAtkObj);
180 static gint getChildCountCB(AtkObject* aAtkObj);
181 static AtkObject* refChildCB(AtkObject* aAtkObj, gint aChildIndex);
182 static gint getIndexInParentCB(AtkObject* aAtkObj);
183 static AtkStateSet* refStateSetCB(AtkObject* aAtkObj);
184 static AtkRelationSet* refRelationSetCB(AtkObject* aAtkObj);
186 /* the missing atkobject virtual functions */
188 static AtkLayer getLayerCB(AtkObject *aAtkObj);
189 static gint getMdiZorderCB(AtkObject *aAtkObj);
190 static void SetNameCB(AtkObject *aAtkObj,
191 const gchar *name);
192 static void SetDescriptionCB(AtkObject *aAtkObj,
193 const gchar *description);
194 static void SetParentCB(AtkObject *aAtkObj,
195 AtkObject *parent);
196 static void SetRoleCB(AtkObject *aAtkObj,
197 AtkRole role);
198 static guint ConnectPropertyChangeHandlerCB(
199 AtkObject *aObj,
200 AtkPropertyChangeHandler *handler);
201 static void RemovePropertyChangeHandlerCB(
202 AtkObject *aAtkObj,
203 guint handler_id);
204 static void InitializeCB(AtkObject *aAtkObj,
205 gpointer data);
206 static void ChildrenChangedCB(AtkObject *aAtkObj,
207 guint change_index,
208 gpointer changed_child);
209 static void FocusEventCB(AtkObject *aAtkObj,
210 gboolean focus_in);
211 static void PropertyChangeCB(AtkObject *aAtkObj,
212 AtkPropertyValues *values);
213 static void StateChangeCB(AtkObject *aAtkObj,
214 const gchar *name,
215 gboolean state_set);
216 static void VisibleDataChangedCB(AtkObject *aAtkObj);
218 G_END_DECLS
220 static GType GetMaiAtkType(uint16_t interfacesBits);
221 static const char* GetUniqueMaiAtkTypeName(uint16_t interfacesBits);
223 static gpointer parent_class = nullptr;
225 GType mai_atk_object_get_type(void) {
226 static GType type = 0;
228 if (!type) {
229 static const GTypeInfo tinfo = {
230 sizeof(MaiAtkObjectClass),
231 (GBaseInitFunc) nullptr,
232 (GBaseFinalizeFunc) nullptr,
233 (GClassInitFunc)classInitCB,
234 (GClassFinalizeFunc) nullptr,
235 nullptr, /* class data */
236 sizeof(MaiAtkObject), /* instance size */
237 0, /* nb preallocs */
238 (GInstanceInitFunc) nullptr,
239 nullptr /* value table */
242 type = g_type_register_static(ATK_TYPE_OBJECT, "MaiAtkObject", &tinfo,
243 GTypeFlags(0));
244 quark_mai_hyperlink = g_quark_from_static_string("MaiHyperlink");
246 return type;
249 AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
250 : LocalAccessible(aContent, aDoc), mAtkObject(nullptr) {}
252 AccessibleWrap::~AccessibleWrap() {
253 NS_ASSERTION(!mAtkObject, "ShutdownAtkObject() is not called");
256 void AccessibleWrap::ShutdownAtkObject() {
257 if (!mAtkObject) return;
259 NS_ASSERTION(IS_MAI_OBJECT(mAtkObject), "wrong type of atk object");
260 if (IS_MAI_OBJECT(mAtkObject)) MAI_ATK_OBJECT(mAtkObject)->Shutdown();
262 g_object_unref(mAtkObject);
263 mAtkObject = nullptr;
266 void AccessibleWrap::Shutdown() {
267 ShutdownAtkObject();
268 LocalAccessible::Shutdown();
271 void AccessibleWrap::GetNativeInterface(void** aOutAccessible) {
272 *aOutAccessible = nullptr;
274 if (!mAtkObject) {
275 if (IsDefunct() || IsText()) {
276 // We don't create ATK objects for node which has been shutdown or
277 // plain text leaves
278 return;
281 GType type = GetMaiAtkType(CreateMaiInterfaces());
282 if (!type) return;
284 mAtkObject = reinterpret_cast<AtkObject*>(g_object_new(type, nullptr));
285 if (!mAtkObject) return;
287 atk_object_initialize(mAtkObject, this);
288 mAtkObject->role = ATK_ROLE_INVALID;
289 mAtkObject->layer = ATK_LAYER_INVALID;
292 *aOutAccessible = mAtkObject;
295 AtkObject* AccessibleWrap::GetAtkObject(void) {
296 void* atkObj = nullptr;
297 GetNativeInterface(&atkObj);
298 return static_cast<AtkObject*>(atkObj);
301 // Get AtkObject from LocalAccessible interface
302 /* static */
303 AtkObject* AccessibleWrap::GetAtkObject(LocalAccessible* acc) {
304 void* atkObjPtr = nullptr;
305 acc->GetNativeInterface(&atkObjPtr);
306 return atkObjPtr ? ATK_OBJECT(atkObjPtr) : nullptr;
309 /* private */
310 uint16_t AccessibleWrap::CreateMaiInterfaces(void) {
311 uint16_t interfacesBits = 0;
313 // The Component interface is supported by all accessibles.
314 interfacesBits |= 1 << MAI_INTERFACE_COMPONENT;
316 // Add Action interface if the action count is more than zero.
317 if (ActionCount() > 0) interfacesBits |= 1 << MAI_INTERFACE_ACTION;
319 // Text, Editabletext, and Hypertext interface.
320 HyperTextAccessible* hyperText = AsHyperText();
321 if (hyperText && hyperText->IsTextRole()) {
322 interfacesBits |= 1 << MAI_INTERFACE_TEXT;
323 interfacesBits |= 1 << MAI_INTERFACE_EDITABLE_TEXT;
324 if (!nsAccUtils::MustPrune(this)) {
325 interfacesBits |= 1 << MAI_INTERFACE_HYPERTEXT;
329 // Value interface.
330 if (HasNumericValue()) interfacesBits |= 1 << MAI_INTERFACE_VALUE;
332 // Document interface.
333 if (IsDoc()) interfacesBits |= 1 << MAI_INTERFACE_DOCUMENT;
335 if (IsImage()) interfacesBits |= 1 << MAI_INTERFACE_IMAGE;
337 // HyperLink interface.
338 if (IsLink()) interfacesBits |= 1 << MAI_INTERFACE_HYPERLINK_IMPL;
340 if (!nsAccUtils::MustPrune(this)) { // These interfaces require children
341 // Table interface.
342 if (AsTable()) interfacesBits |= 1 << MAI_INTERFACE_TABLE;
344 if (AsTableCell()) interfacesBits |= 1 << MAI_INTERFACE_TABLE_CELL;
346 // Selection interface.
347 if (IsSelect()) {
348 interfacesBits |= 1 << MAI_INTERFACE_SELECTION;
352 return interfacesBits;
355 static GType GetMaiAtkType(uint16_t interfacesBits) {
356 GType type;
357 static const GTypeInfo tinfo = {
358 sizeof(MaiAtkObjectClass),
359 (GBaseInitFunc) nullptr,
360 (GBaseFinalizeFunc) nullptr,
361 (GClassInitFunc) nullptr,
362 (GClassFinalizeFunc) nullptr,
363 nullptr, /* class data */
364 sizeof(MaiAtkObject), /* instance size */
365 0, /* nb preallocs */
366 (GInstanceInitFunc) nullptr,
367 nullptr /* value table */
371 * The members we use to register GTypes are GetAtkTypeForMai
372 * and atk_if_infos, which are constant values to each MaiInterface
373 * So we can reuse the registered GType when having
374 * the same MaiInterface types.
376 const char* atkTypeName = GetUniqueMaiAtkTypeName(interfacesBits);
377 type = g_type_from_name(atkTypeName);
378 if (type) {
379 return type;
383 * gobject limits the number of types that can directly derive from any
384 * given object type to 4095.
386 static uint16_t typeRegCount = 0;
387 if (typeRegCount++ >= 4095) {
388 return G_TYPE_INVALID;
390 type = g_type_register_static(MAI_TYPE_ATK_OBJECT, atkTypeName, &tinfo,
391 GTypeFlags(0));
393 for (uint32_t index = 0; index < ArrayLength(atk_if_infos); index++) {
394 if (interfacesBits & (1 << index)) {
395 g_type_add_interface_static(type,
396 GetAtkTypeForMai((MaiInterfaceType)index),
397 &atk_if_infos[index]);
401 // Special case AtkTableCell so we can check what version of Atk we are
402 // dealing with.
403 if (IsAtkVersionAtLeast(2, 12) &&
404 (interfacesBits & (1 << MAI_INTERFACE_TABLE_CELL))) {
405 const GInterfaceInfo cellInfo = {
406 (GInterfaceInitFunc)tableCellInterfaceInitCB,
407 (GInterfaceFinalizeFunc) nullptr, nullptr};
408 g_type_add_interface_static(type, gAtkTableCellGetTypeFunc(), &cellInfo);
411 return type;
414 static const char* GetUniqueMaiAtkTypeName(uint16_t interfacesBits) {
415 #define MAI_ATK_TYPE_NAME_LEN (30) /* 10+sizeof(uint16_t)*8/4+1 < 30 */
417 static gchar namePrefix[] = "MaiAtkType"; /* size = 10 */
418 static gchar name[MAI_ATK_TYPE_NAME_LEN + 1];
420 SprintfLiteral(name, "%s%x", namePrefix, interfacesBits);
421 name[MAI_ATK_TYPE_NAME_LEN] = '\0';
423 return name;
426 bool AccessibleWrap::IsValidObject() {
427 // to ensure we are not shut down
428 return !IsDefunct();
431 /* static functions for ATK callbacks */
432 void classInitCB(AtkObjectClass* aClass) {
433 GObjectClass* gobject_class = G_OBJECT_CLASS(aClass);
435 parent_class = g_type_class_peek_parent(aClass);
437 aClass->get_name = getNameCB;
438 aClass->get_description = getDescriptionCB;
439 aClass->get_parent = getParentCB;
440 aClass->get_n_children = getChildCountCB;
441 aClass->ref_child = refChildCB;
442 aClass->get_index_in_parent = getIndexInParentCB;
443 aClass->get_role = getRoleCB;
444 aClass->get_attributes = getAttributesCB;
445 aClass->get_object_locale = GetLocaleCB;
446 aClass->ref_state_set = refStateSetCB;
447 aClass->ref_relation_set = refRelationSetCB;
449 aClass->initialize = initializeCB;
451 gobject_class->finalize = finalizeCB;
453 mai_atk_object_signals[ACTIVATE] = g_signal_new(
454 "activate", MAI_TYPE_ATK_OBJECT, G_SIGNAL_RUN_LAST,
455 0, /* default signal handler */
456 nullptr, nullptr, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
457 mai_atk_object_signals[CREATE] = g_signal_new(
458 "create", MAI_TYPE_ATK_OBJECT, G_SIGNAL_RUN_LAST,
459 0, /* default signal handler */
460 nullptr, nullptr, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
461 mai_atk_object_signals[DEACTIVATE] = g_signal_new(
462 "deactivate", MAI_TYPE_ATK_OBJECT, G_SIGNAL_RUN_LAST,
463 0, /* default signal handler */
464 nullptr, nullptr, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
465 mai_atk_object_signals[DESTROY] = g_signal_new(
466 "destroy", MAI_TYPE_ATK_OBJECT, G_SIGNAL_RUN_LAST,
467 0, /* default signal handler */
468 nullptr, nullptr, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
469 mai_atk_object_signals[MAXIMIZE] = g_signal_new(
470 "maximize", MAI_TYPE_ATK_OBJECT, G_SIGNAL_RUN_LAST,
471 0, /* default signal handler */
472 nullptr, nullptr, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
473 mai_atk_object_signals[MINIMIZE] = g_signal_new(
474 "minimize", MAI_TYPE_ATK_OBJECT, G_SIGNAL_RUN_LAST,
475 0, /* default signal handler */
476 nullptr, nullptr, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
477 mai_atk_object_signals[RESIZE] = g_signal_new(
478 "resize", MAI_TYPE_ATK_OBJECT, G_SIGNAL_RUN_LAST,
479 0, /* default signal handler */
480 nullptr, nullptr, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
481 mai_atk_object_signals[RESTORE] = g_signal_new(
482 "restore", MAI_TYPE_ATK_OBJECT, G_SIGNAL_RUN_LAST,
483 0, /* default signal handler */
484 nullptr, nullptr, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
487 void initializeCB(AtkObject* aAtkObj, gpointer aData) {
488 NS_ASSERTION((IS_MAI_OBJECT(aAtkObj)), "Invalid AtkObject");
489 NS_ASSERTION(aData, "Invalid Data to init AtkObject");
490 if (!aAtkObj || !aData) return;
492 /* call parent init function */
493 /* AtkObjectClass has not a "initialize" function now,
494 * maybe it has later
497 if (ATK_OBJECT_CLASS(parent_class)->initialize) {
498 ATK_OBJECT_CLASS(parent_class)->initialize(aAtkObj, aData);
501 /* initialize object */
502 MAI_ATK_OBJECT(aAtkObj)->accWrap.SetBits(reinterpret_cast<uintptr_t>(aData));
505 void finalizeCB(GObject* aObj) {
506 if (!IS_MAI_OBJECT(aObj)) return;
507 NS_ASSERTION(MAI_ATK_OBJECT(aObj)->accWrap.IsNull(), "AccWrap NOT null");
509 // call parent finalize function
510 // finalize of GObjectClass will unref the accessible parent if has
511 if (G_OBJECT_CLASS(parent_class)->finalize) {
512 G_OBJECT_CLASS(parent_class)->finalize(aObj);
516 const gchar* getNameCB(AtkObject* aAtkObj) {
517 nsAutoString name;
518 AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
519 if (accWrap) {
520 accWrap->Name(name);
521 } else if (RemoteAccessible* proxy = GetProxy(aAtkObj)) {
522 proxy->Name(name);
523 } else {
524 return nullptr;
527 // XXX Firing an event from here does not seem right
528 MaybeFireNameChange(aAtkObj, name);
530 return aAtkObj->name;
533 static void MaybeFireNameChange(AtkObject* aAtkObj, const nsString& aNewName) {
534 NS_ConvertUTF16toUTF8 newNameUTF8(aNewName);
535 if (aAtkObj->name && !strcmp(aAtkObj->name, newNameUTF8.get())) return;
537 // Below we duplicate the functionality of atk_object_set_name(),
538 // but without calling atk_object_get_name(). Instead of
539 // atk_object_get_name() we directly access aAtkObj->name. This is because
540 // atk_object_get_name() would call getNameCB() which would call
541 // MaybeFireNameChange() (or atk_object_set_name() before this problem was
542 // fixed) and we would get an infinite recursion.
543 // See http://bugzilla.mozilla.org/733712
545 // Do not notify for initial name setting.
546 // See bug http://bugzilla.gnome.org/665870
547 bool notify = !!aAtkObj->name;
549 free(aAtkObj->name);
550 aAtkObj->name = strdup(newNameUTF8.get());
552 if (notify) g_object_notify(G_OBJECT(aAtkObj), "accessible-name");
555 const gchar* getDescriptionCB(AtkObject* aAtkObj) {
556 nsAutoString uniDesc;
557 AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
558 if (accWrap) {
559 if (accWrap->IsDefunct()) return nullptr;
561 accWrap->Description(uniDesc);
562 } else if (RemoteAccessible* proxy = GetProxy(aAtkObj)) {
563 proxy->Description(uniDesc);
564 } else {
565 return nullptr;
568 NS_ConvertUTF8toUTF16 objDesc(aAtkObj->description);
569 if (!uniDesc.Equals(objDesc)) {
570 atk_object_set_description(aAtkObj, NS_ConvertUTF16toUTF8(uniDesc).get());
573 return aAtkObj->description;
576 AtkRole getRoleCB(AtkObject* aAtkObj) {
577 if (aAtkObj->role != ATK_ROLE_INVALID) return aAtkObj->role;
579 AccessibleOrProxy acc = GetInternalObj(aAtkObj);
580 if (acc.IsNull()) {
581 return ATK_ROLE_INVALID;
584 #ifdef DEBUG
585 if (AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj)) {
586 NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(accWrap),
587 "Does not support Text interface when it should");
589 #endif
591 #define ROLE(geckoRole, stringRole, atkRole, macRole, macSubrole, msaaRole, \
592 ia2Role, androidClass, nameRule) \
593 case roles::geckoRole: \
594 aAtkObj->role = atkRole; \
595 break;
597 switch (acc.Role()) {
598 #include "RoleMap.h"
599 default:
600 MOZ_CRASH("Unknown role.");
603 #undef ROLE
605 if (aAtkObj->role == ATK_ROLE_LIST_BOX && !IsAtkVersionAtLeast(2, 1)) {
606 aAtkObj->role = ATK_ROLE_LIST;
607 } else if (aAtkObj->role == ATK_ROLE_TABLE_ROW &&
608 !IsAtkVersionAtLeast(2, 1)) {
609 aAtkObj->role = ATK_ROLE_LIST_ITEM;
610 } else if (aAtkObj->role == ATK_ROLE_MATH && !IsAtkVersionAtLeast(2, 12)) {
611 aAtkObj->role = ATK_ROLE_SECTION;
612 } else if (aAtkObj->role == ATK_ROLE_COMMENT && !IsAtkVersionAtLeast(2, 12)) {
613 aAtkObj->role = ATK_ROLE_SECTION;
614 } else if (aAtkObj->role == ATK_ROLE_LANDMARK &&
615 !IsAtkVersionAtLeast(2, 12)) {
616 aAtkObj->role = ATK_ROLE_SECTION;
617 } else if (aAtkObj->role == ATK_ROLE_FOOTNOTE &&
618 !IsAtkVersionAtLeast(2, 25, 2)) {
619 aAtkObj->role = ATK_ROLE_SECTION;
620 } else if (aAtkObj->role == ATK_ROLE_STATIC && !IsAtkVersionAtLeast(2, 16)) {
621 aAtkObj->role = ATK_ROLE_TEXT;
622 } else if ((aAtkObj->role == ATK_ROLE_MATH_FRACTION ||
623 aAtkObj->role == ATK_ROLE_MATH_ROOT) &&
624 !IsAtkVersionAtLeast(2, 16)) {
625 aAtkObj->role = ATK_ROLE_SECTION;
626 } else if (aAtkObj->role == ATK_ROLE_MARK && !IsAtkVersionAtLeast(2, 36)) {
627 aAtkObj->role = ATK_ROLE_TEXT;
628 } else if (aAtkObj->role == ATK_ROLE_SUGGESTION &&
629 !IsAtkVersionAtLeast(2, 36)) {
630 aAtkObj->role = ATK_ROLE_SECTION;
631 } else if (aAtkObj->role == ATK_ROLE_COMMENT && !IsAtkVersionAtLeast(2, 36)) {
632 aAtkObj->role = ATK_ROLE_SECTION;
635 return aAtkObj->role;
638 static AtkAttributeSet* ConvertToAtkAttributeSet(
639 nsIPersistentProperties* aAttributes) {
640 if (!aAttributes) return nullptr;
642 AtkAttributeSet* objAttributeSet = nullptr;
643 nsCOMPtr<nsISimpleEnumerator> propEnum;
644 nsresult rv = aAttributes->Enumerate(getter_AddRefs(propEnum));
645 NS_ENSURE_SUCCESS(rv, nullptr);
647 bool hasMore;
648 while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) && hasMore) {
649 nsCOMPtr<nsISupports> sup;
650 rv = propEnum->GetNext(getter_AddRefs(sup));
651 NS_ENSURE_SUCCESS(rv, objAttributeSet);
653 nsCOMPtr<nsIPropertyElement> propElem(do_QueryInterface(sup));
654 NS_ENSURE_TRUE(propElem, objAttributeSet);
656 nsAutoCString name;
657 rv = propElem->GetKey(name);
658 NS_ENSURE_SUCCESS(rv, objAttributeSet);
660 nsAutoString value;
661 rv = propElem->GetValue(value);
662 NS_ENSURE_SUCCESS(rv, objAttributeSet);
664 // On ATK, the placeholder attribute is called placeholder-text.
665 if (name.Equals("placeholder")) {
666 name.AssignLiteral("placeholder-text");
669 AtkAttribute* objAttr = (AtkAttribute*)g_malloc(sizeof(AtkAttribute));
670 objAttr->name = g_strdup(name.get());
671 objAttr->value = g_strdup(NS_ConvertUTF16toUTF8(value).get());
672 objAttributeSet = g_slist_prepend(objAttributeSet, objAttr);
675 // libspi will free it
676 return objAttributeSet;
679 AtkAttributeSet* GetAttributeSet(LocalAccessible* aAccessible) {
680 nsCOMPtr<nsIPersistentProperties> attributes = aAccessible->Attributes();
681 if (attributes) return ConvertToAtkAttributeSet(attributes);
683 return nullptr;
686 AtkAttributeSet* getAttributesCB(AtkObject* aAtkObj) {
687 AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
688 if (accWrap) return GetAttributeSet(accWrap);
690 RemoteAccessible* proxy = GetProxy(aAtkObj);
691 if (!proxy) return nullptr;
693 AutoTArray<Attribute, 10> attrs;
694 proxy->Attributes(&attrs);
695 if (attrs.IsEmpty()) return nullptr;
697 AtkAttributeSet* objAttributeSet = nullptr;
698 for (uint32_t i = 0; i < attrs.Length(); i++) {
699 AtkAttribute* objAttr = (AtkAttribute*)g_malloc(sizeof(AtkAttribute));
700 // On ATK, the placeholder attribute is called placeholder-text.
701 if (attrs[i].Name().Equals("placeholder")) {
702 attrs[i].Name().AssignLiteral("placeholder-text");
704 objAttr->name = g_strdup(attrs[i].Name().get());
705 objAttr->value = g_strdup(NS_ConvertUTF16toUTF8(attrs[i].Value()).get());
706 objAttributeSet = g_slist_prepend(objAttributeSet, objAttr);
709 return objAttributeSet;
712 const gchar* GetLocaleCB(AtkObject* aAtkObj) {
713 AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
714 if (!accWrap) return nullptr;
716 nsAutoString locale;
717 accWrap->Language(locale);
718 return AccessibleWrap::ReturnString(locale);
721 AtkObject* getParentCB(AtkObject* aAtkObj) {
722 if (aAtkObj->accessible_parent) return aAtkObj->accessible_parent;
724 AccessibleOrProxy acc = GetInternalObj(aAtkObj);
725 if (acc.IsNull()) {
726 return nullptr;
729 AccessibleOrProxy parent = acc.Parent();
730 AtkObject* atkParent = !parent.IsNull() ? GetWrapperFor(parent) : nullptr;
731 if (atkParent) atk_object_set_parent(aAtkObj, atkParent);
733 return aAtkObj->accessible_parent;
736 gint getChildCountCB(AtkObject* aAtkObj) {
737 if (AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj)) {
738 if (nsAccUtils::MustPrune(accWrap)) {
739 return 0;
742 uint32_t count = accWrap->EmbeddedChildCount();
743 if (count) {
744 return static_cast<gint>(count);
747 OuterDocAccessible* outerDoc = accWrap->AsOuterDoc();
748 if (outerDoc && outerDoc->RemoteChildDoc()) {
749 return 1;
753 RemoteAccessible* proxy = GetProxy(aAtkObj);
754 if (proxy && !nsAccUtils::MustPrune(proxy)) {
755 return proxy->EmbeddedChildCount();
758 return 0;
761 AtkObject* refChildCB(AtkObject* aAtkObj, gint aChildIndex) {
762 // aChildIndex should not be less than zero
763 if (aChildIndex < 0) {
764 return nullptr;
767 AtkObject* childAtkObj = nullptr;
768 AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
769 if (accWrap) {
770 if (nsAccUtils::MustPrune(accWrap)) {
771 return nullptr;
774 LocalAccessible* accChild = accWrap->GetEmbeddedChildAt(aChildIndex);
775 if (accChild) {
776 childAtkObj = AccessibleWrap::GetAtkObject(accChild);
777 } else {
778 OuterDocAccessible* docOwner = accWrap->AsOuterDoc();
779 if (docOwner) {
780 RemoteAccessible* proxyDoc = docOwner->RemoteChildDoc();
781 if (proxyDoc) childAtkObj = GetWrapperFor(proxyDoc);
784 } else if (RemoteAccessible* proxy = GetProxy(aAtkObj)) {
785 if (nsAccUtils::MustPrune(proxy)) {
786 return nullptr;
789 RemoteAccessible* child = proxy->EmbeddedChildAt(aChildIndex);
790 if (child) childAtkObj = GetWrapperFor(child);
791 } else {
792 return nullptr;
795 NS_ASSERTION(childAtkObj, "Fail to get AtkObj");
796 if (!childAtkObj) return nullptr;
798 g_object_ref(childAtkObj);
800 if (aAtkObj != childAtkObj->accessible_parent) {
801 atk_object_set_parent(childAtkObj, aAtkObj);
804 return childAtkObj;
807 gint getIndexInParentCB(AtkObject* aAtkObj) {
808 // We don't use LocalAccessible::IndexInParent() because we don't include text
809 // leaf nodes as children in ATK.
810 if (RemoteAccessible* proxy = GetProxy(aAtkObj)) {
811 if (RemoteAccessible* parent = proxy->RemoteParent()) {
812 return parent->IndexOfEmbeddedChild(proxy);
815 if (proxy->OuterDocOfRemoteBrowser()) {
816 return 0;
819 return -1;
822 AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
823 if (!accWrap) {
824 return -1;
827 LocalAccessible* parent = accWrap->LocalParent();
828 if (!parent) return -1; // No parent
830 return parent->GetIndexOfEmbeddedChild(accWrap);
833 static void TranslateStates(uint64_t aState, roles::Role aRole,
834 AtkStateSet* aStateSet) {
835 // atk doesn't have a read only state so read only things shouldn't be
836 // editable. However, we don't do this for list items because Gecko always
837 // exposes those as read only.
838 if ((aState & states::READONLY) && aRole != roles::LISTITEM) {
839 aState &= ~states::EDITABLE;
842 // Convert every state to an entry in AtkStateMap
843 uint64_t bitMask = 1;
844 for (auto stateIndex = 0U; stateIndex < gAtkStateMapLen; stateIndex++) {
845 if (gAtkStateMap[stateIndex]
846 .atkState) { // There's potentially an ATK state for this
847 bool isStateOn = (aState & bitMask) != 0;
848 if (gAtkStateMap[stateIndex].stateMapEntryType == kMapOpposite) {
849 isStateOn = !isStateOn;
851 if (isStateOn) {
852 atk_state_set_add_state(aStateSet, gAtkStateMap[stateIndex].atkState);
855 bitMask <<= 1;
859 AtkStateSet* refStateSetCB(AtkObject* aAtkObj) {
860 AtkStateSet* state_set = nullptr;
861 state_set = ATK_OBJECT_CLASS(parent_class)->ref_state_set(aAtkObj);
863 AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
864 if (accWrap) {
865 TranslateStates(accWrap->State(), accWrap->Role(), state_set);
866 } else if (RemoteAccessible* proxy = GetProxy(aAtkObj)) {
867 TranslateStates(proxy->State(), proxy->Role(), state_set);
868 } else {
869 TranslateStates(states::DEFUNCT, roles::NOTHING, state_set);
872 return state_set;
875 static void UpdateAtkRelation(RelationType aType, LocalAccessible* aAcc,
876 AtkRelationType aAtkType,
877 AtkRelationSet* aAtkSet) {
878 if (aAtkType == ATK_RELATION_NULL) return;
880 AtkRelation* atkRelation =
881 atk_relation_set_get_relation_by_type(aAtkSet, aAtkType);
882 if (atkRelation) atk_relation_set_remove(aAtkSet, atkRelation);
884 Relation rel(aAcc->RelationByType(aType));
885 nsTArray<AtkObject*> targets;
886 LocalAccessible* tempAcc = nullptr;
887 while ((tempAcc = rel.Next())) {
888 targets.AppendElement(AccessibleWrap::GetAtkObject(tempAcc));
891 if (aType == RelationType::EMBEDS && aAcc->IsRoot()) {
892 if (RemoteAccessible* proxyDoc =
893 aAcc->AsRoot()->GetPrimaryRemoteTopLevelContentDoc()) {
894 targets.AppendElement(GetWrapperFor(proxyDoc));
898 if (targets.Length()) {
899 atkRelation =
900 atk_relation_new(targets.Elements(), targets.Length(), aAtkType);
901 atk_relation_set_add(aAtkSet, atkRelation);
902 g_object_unref(atkRelation);
906 AtkRelationSet* refRelationSetCB(AtkObject* aAtkObj) {
907 AtkRelationSet* relation_set =
908 ATK_OBJECT_CLASS(parent_class)->ref_relation_set(aAtkObj);
910 const AtkRelationType typeMap[] = {
911 #define RELATIONTYPE(gecko, s, atk, m, i) atk,
912 #include "RelationTypeMap.h"
913 #undef RELATIONTYPE
916 if (RemoteAccessible* proxy = GetProxy(aAtkObj)) {
917 nsTArray<RelationType> types;
918 nsTArray<nsTArray<RemoteAccessible*>> targetSets;
919 proxy->Relations(&types, &targetSets);
921 size_t relationCount = types.Length();
922 for (size_t i = 0; i < relationCount; i++) {
923 if (typeMap[static_cast<uint32_t>(types[i])] == ATK_RELATION_NULL) {
924 continue;
927 size_t targetCount = targetSets[i].Length();
928 AutoTArray<AtkObject*, 5> wrappers;
929 for (size_t j = 0; j < targetCount; j++) {
930 wrappers.AppendElement(GetWrapperFor(targetSets[i][j]));
933 AtkRelationType atkType = typeMap[static_cast<uint32_t>(types[i])];
934 AtkRelation* atkRelation =
935 atk_relation_set_get_relation_by_type(relation_set, atkType);
936 if (atkRelation) atk_relation_set_remove(relation_set, atkRelation);
938 atkRelation =
939 atk_relation_new(wrappers.Elements(), wrappers.Length(), atkType);
940 atk_relation_set_add(relation_set, atkRelation);
941 g_object_unref(atkRelation);
945 AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
946 if (!accWrap) return relation_set;
948 #define RELATIONTYPE(geckoType, geckoTypeName, atkType, msaaType, ia2Type) \
949 UpdateAtkRelation(RelationType::geckoType, accWrap, atkType, relation_set);
951 #include "RelationTypeMap.h"
953 #undef RELATIONTYPE
955 return relation_set;
958 // Check if aAtkObj is a valid MaiAtkObject, and return the AccessibleWrap
959 // for it.
960 AccessibleWrap* GetAccessibleWrap(AtkObject* aAtkObj) {
961 bool isMAIObject = IS_MAI_OBJECT(aAtkObj);
962 NS_ENSURE_TRUE(isMAIObject || MAI_IS_ATK_SOCKET(aAtkObj), nullptr);
964 AccessibleWrap* accWrap = nullptr;
965 if (isMAIObject) {
966 LocalAccessible* acc = MAI_ATK_OBJECT(aAtkObj)->accWrap.AsAccessible();
967 accWrap = static_cast<AccessibleWrap*>(acc);
968 } else {
969 accWrap = MAI_ATK_SOCKET(aAtkObj)->accWrap;
972 // Check if the accessible was deconstructed.
973 if (!accWrap) return nullptr;
975 NS_ENSURE_TRUE(accWrap->GetAtkObject() == aAtkObj, nullptr);
977 AccessibleWrap* appAccWrap = ApplicationAcc();
978 if (appAccWrap != accWrap && !accWrap->IsValidObject()) return nullptr;
980 return accWrap;
983 RemoteAccessible* GetProxy(AtkObject* aObj) {
984 return GetInternalObj(aObj).AsProxy();
987 AccessibleOrProxy GetInternalObj(AtkObject* aObj) {
988 if (!aObj || !IS_MAI_OBJECT(aObj)) return nullptr;
990 return MAI_ATK_OBJECT(aObj)->accWrap;
993 AtkObject* GetWrapperFor(RemoteAccessible* aProxy) {
994 return reinterpret_cast<AtkObject*>(aProxy->GetWrapper() & ~IS_PROXY);
997 AtkObject* GetWrapperFor(AccessibleOrProxy aObj) {
998 if (aObj.IsProxy()) {
999 return GetWrapperFor(aObj.AsProxy());
1002 return AccessibleWrap::GetAtkObject(aObj.AsAccessible());
1005 static uint16_t GetInterfacesForProxy(RemoteAccessible* aProxy) {
1006 uint16_t interfaces = 1 << MAI_INTERFACE_COMPONENT;
1007 if (aProxy->IsHyperText()) {
1008 interfaces |= (1 << MAI_INTERFACE_HYPERTEXT) | (1 << MAI_INTERFACE_TEXT) |
1009 (1 << MAI_INTERFACE_EDITABLE_TEXT);
1012 if (aProxy->IsLink()) {
1013 interfaces |= 1 << MAI_INTERFACE_HYPERLINK_IMPL;
1016 if (aProxy->HasNumericValue()) {
1017 interfaces |= 1 << MAI_INTERFACE_VALUE;
1020 if (aProxy->IsTable()) {
1021 interfaces |= 1 << MAI_INTERFACE_TABLE;
1024 if (aProxy->IsTableCell()) {
1025 interfaces |= 1 << MAI_INTERFACE_TABLE_CELL;
1028 if (aProxy->IsImage()) {
1029 interfaces |= 1 << MAI_INTERFACE_IMAGE;
1032 if (aProxy->IsDoc()) {
1033 interfaces |= 1 << MAI_INTERFACE_DOCUMENT;
1036 if (aProxy->IsSelect()) {
1037 interfaces |= 1 << MAI_INTERFACE_SELECTION;
1040 if (aProxy->IsActionable()) {
1041 interfaces |= 1 << MAI_INTERFACE_ACTION;
1044 return interfaces;
1047 void a11y::ProxyCreated(RemoteAccessible* aProxy) {
1048 GType type = GetMaiAtkType(GetInterfacesForProxy(aProxy));
1049 NS_ASSERTION(type, "why don't we have a type!");
1051 AtkObject* obj = reinterpret_cast<AtkObject*>(g_object_new(type, nullptr));
1052 if (!obj) return;
1054 uintptr_t inner = reinterpret_cast<uintptr_t>(aProxy) | IS_PROXY;
1055 atk_object_initialize(obj, reinterpret_cast<gpointer>(inner));
1056 obj->role = ATK_ROLE_INVALID;
1057 obj->layer = ATK_LAYER_INVALID;
1058 aProxy->SetWrapper(reinterpret_cast<uintptr_t>(obj) | IS_PROXY);
1061 void a11y::ProxyDestroyed(RemoteAccessible* aProxy) {
1062 auto obj = reinterpret_cast<MaiAtkObject*>(aProxy->GetWrapper() & ~IS_PROXY);
1063 if (!obj) {
1064 return;
1067 obj->Shutdown();
1068 g_object_unref(obj);
1069 aProxy->SetWrapper(0);
1072 nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) {
1073 nsresult rv = LocalAccessible::HandleAccEvent(aEvent);
1074 NS_ENSURE_SUCCESS(rv, rv);
1076 if (IPCAccessibilityActive()) {
1077 return NS_OK;
1080 LocalAccessible* accessible = aEvent->GetAccessible();
1081 NS_ENSURE_TRUE(accessible, NS_ERROR_FAILURE);
1083 // The accessible can become defunct if we have an xpcom event listener
1084 // which decides it would be fun to change the DOM and flush layout.
1085 if (accessible->IsDefunct()) return NS_OK;
1087 uint32_t type = aEvent->GetEventType();
1089 AtkObject* atkObj = AccessibleWrap::GetAtkObject(accessible);
1091 // We don't create ATK objects for plain text leaves, just return NS_OK in
1092 // such case.
1093 if (!atkObj) {
1094 NS_ASSERTION(type == nsIAccessibleEvent::EVENT_SHOW ||
1095 type == nsIAccessibleEvent::EVENT_HIDE,
1096 "Event other than SHOW and HIDE fired for plain text leaves");
1097 return NS_OK;
1100 AccessibleWrap* accWrap = GetAccessibleWrap(atkObj);
1101 if (!accWrap) {
1102 return NS_OK; // Node is shut down
1105 switch (type) {
1106 case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
1107 AccStateChangeEvent* event = downcast_accEvent(aEvent);
1108 MAI_ATK_OBJECT(atkObj)->FireStateChangeEvent(event->GetState(),
1109 event->IsStateEnabled());
1110 break;
1113 case nsIAccessibleEvent::EVENT_TEXT_REMOVED:
1114 case nsIAccessibleEvent::EVENT_TEXT_INSERTED: {
1115 AccTextChangeEvent* event = downcast_accEvent(aEvent);
1116 NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
1118 MAI_ATK_OBJECT(atkObj)->FireTextChangeEvent(
1119 event->ModifiedText(), event->GetStartOffset(), event->GetLength(),
1120 event->IsTextInserted(), event->IsFromUserInput());
1122 return NS_OK;
1125 case nsIAccessibleEvent::EVENT_FOCUS: {
1126 a11y::RootAccessible* rootAccWrap = accWrap->RootAccessible();
1127 if (rootAccWrap && rootAccWrap->mActivated) {
1128 atk_focus_tracker_notify(atkObj);
1129 // Fire state change event for focus
1130 atk_object_notify_state_change(atkObj, ATK_STATE_FOCUSED, true);
1131 return NS_OK;
1133 } break;
1135 case nsIAccessibleEvent::EVENT_NAME_CHANGE: {
1136 nsAutoString newName;
1137 accessible->Name(newName);
1139 MaybeFireNameChange(atkObj, newName);
1141 break;
1144 case nsIAccessibleEvent::EVENT_VALUE_CHANGE:
1145 case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE:
1146 if (accessible->HasNumericValue()) {
1147 // Make sure this is a numeric value. Don't fire for string value
1148 // changes (e.g. text editing) ATK values are always numeric.
1149 g_object_notify((GObject*)atkObj, "accessible-value");
1151 break;
1153 case nsIAccessibleEvent::EVENT_SELECTION:
1154 case nsIAccessibleEvent::EVENT_SELECTION_ADD:
1155 case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
1156 // XXX: dupe events may be fired
1157 AccSelChangeEvent* selChangeEvent = downcast_accEvent(aEvent);
1158 g_signal_emit_by_name(
1159 AccessibleWrap::GetAtkObject(selChangeEvent->Widget()),
1160 "selection_changed");
1161 break;
1164 case nsIAccessibleEvent::EVENT_SELECTION_WITHIN: {
1165 g_signal_emit_by_name(atkObj, "selection_changed");
1166 break;
1169 case nsIAccessibleEvent::EVENT_ALERT:
1170 // A hack using state change showing events as alert events.
1171 atk_object_notify_state_change(atkObj, ATK_STATE_SHOWING, true);
1172 break;
1174 case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED:
1175 g_signal_emit_by_name(atkObj, "text_selection_changed");
1176 break;
1178 case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
1179 AccCaretMoveEvent* caretMoveEvent = downcast_accEvent(aEvent);
1180 NS_ASSERTION(caretMoveEvent, "Event needs event data");
1181 if (!caretMoveEvent) break;
1183 int32_t caretOffset = caretMoveEvent->GetCaretOffset();
1184 g_signal_emit_by_name(atkObj, "text_caret_moved", caretOffset);
1185 } break;
1187 case nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED:
1188 g_signal_emit_by_name(atkObj, "text-attributes-changed");
1189 break;
1191 case nsIAccessibleEvent::EVENT_TABLE_MODEL_CHANGED:
1192 g_signal_emit_by_name(atkObj, "model_changed");
1193 break;
1195 case nsIAccessibleEvent::EVENT_TABLE_ROW_INSERT: {
1196 AccTableChangeEvent* tableEvent = downcast_accEvent(aEvent);
1197 NS_ENSURE_TRUE(tableEvent, NS_ERROR_FAILURE);
1199 int32_t rowIndex = tableEvent->GetIndex();
1200 int32_t numRows = tableEvent->GetCount();
1202 g_signal_emit_by_name(atkObj, "row_inserted", rowIndex, numRows);
1203 } break;
1205 case nsIAccessibleEvent::EVENT_TABLE_ROW_DELETE: {
1206 AccTableChangeEvent* tableEvent = downcast_accEvent(aEvent);
1207 NS_ENSURE_TRUE(tableEvent, NS_ERROR_FAILURE);
1209 int32_t rowIndex = tableEvent->GetIndex();
1210 int32_t numRows = tableEvent->GetCount();
1212 g_signal_emit_by_name(atkObj, "row_deleted", rowIndex, numRows);
1213 } break;
1215 case nsIAccessibleEvent::EVENT_TABLE_ROW_REORDER: {
1216 g_signal_emit_by_name(atkObj, "row_reordered");
1217 break;
1220 case nsIAccessibleEvent::EVENT_TABLE_COLUMN_INSERT: {
1221 AccTableChangeEvent* tableEvent = downcast_accEvent(aEvent);
1222 NS_ENSURE_TRUE(tableEvent, NS_ERROR_FAILURE);
1224 int32_t colIndex = tableEvent->GetIndex();
1225 int32_t numCols = tableEvent->GetCount();
1226 g_signal_emit_by_name(atkObj, "column_inserted", colIndex, numCols);
1227 } break;
1229 case nsIAccessibleEvent::EVENT_TABLE_COLUMN_DELETE: {
1230 AccTableChangeEvent* tableEvent = downcast_accEvent(aEvent);
1231 NS_ENSURE_TRUE(tableEvent, NS_ERROR_FAILURE);
1233 int32_t colIndex = tableEvent->GetIndex();
1234 int32_t numCols = tableEvent->GetCount();
1235 g_signal_emit_by_name(atkObj, "column_deleted", colIndex, numCols);
1236 } break;
1238 case nsIAccessibleEvent::EVENT_TABLE_COLUMN_REORDER:
1239 g_signal_emit_by_name(atkObj, "column_reordered");
1240 break;
1242 case nsIAccessibleEvent::EVENT_SECTION_CHANGED:
1243 g_signal_emit_by_name(atkObj, "visible_data_changed");
1244 break;
1246 case nsIAccessibleEvent::EVENT_SHOW: {
1247 AccMutationEvent* event = downcast_accEvent(aEvent);
1248 LocalAccessible* parentAcc =
1249 event ? event->LocalParent() : accessible->LocalParent();
1250 AtkObject* parent = AccessibleWrap::GetAtkObject(parentAcc);
1251 NS_ENSURE_STATE(parent);
1252 auto obj = reinterpret_cast<MaiAtkObject*>(atkObj);
1253 obj->FireAtkShowHideEvent(parent, true, aEvent->IsFromUserInput());
1254 return NS_OK;
1257 case nsIAccessibleEvent::EVENT_HIDE: {
1258 // XXX - Handle native dialog accessibles.
1259 if (!accessible->IsRoot() && accessible->HasARIARole() &&
1260 accessible->ARIARole() == roles::DIALOG) {
1261 guint id = g_signal_lookup("deactivate", MAI_TYPE_ATK_OBJECT);
1262 g_signal_emit(atkObj, id, 0);
1265 AccMutationEvent* event = downcast_accEvent(aEvent);
1266 LocalAccessible* parentAcc =
1267 event ? event->LocalParent() : accessible->LocalParent();
1268 AtkObject* parent = AccessibleWrap::GetAtkObject(parentAcc);
1269 NS_ENSURE_STATE(parent);
1270 auto obj = reinterpret_cast<MaiAtkObject*>(atkObj);
1271 obj->FireAtkShowHideEvent(parent, false, aEvent->IsFromUserInput());
1272 return NS_OK;
1276 * Because dealing with menu is very different between nsIAccessible
1277 * and ATK, and the menu activity is important, specially transfer the
1278 * following two event.
1279 * Need more verification by AT test.
1281 case nsIAccessibleEvent::EVENT_MENU_START:
1282 case nsIAccessibleEvent::EVENT_MENU_END:
1283 break;
1285 case nsIAccessibleEvent::EVENT_WINDOW_ACTIVATE: {
1286 accessible->AsRoot()->mActivated = true;
1287 guint id = g_signal_lookup("activate", MAI_TYPE_ATK_OBJECT);
1288 g_signal_emit(atkObj, id, 0);
1290 // Always fire a current focus event after activation.
1291 FocusMgr()->ForceFocusEvent();
1292 } break;
1294 case nsIAccessibleEvent::EVENT_WINDOW_DEACTIVATE: {
1295 accessible->AsRoot()->mActivated = false;
1296 guint id = g_signal_lookup("deactivate", MAI_TYPE_ATK_OBJECT);
1297 g_signal_emit(atkObj, id, 0);
1298 } break;
1300 case nsIAccessibleEvent::EVENT_WINDOW_MAXIMIZE: {
1301 guint id = g_signal_lookup("maximize", MAI_TYPE_ATK_OBJECT);
1302 g_signal_emit(atkObj, id, 0);
1303 } break;
1305 case nsIAccessibleEvent::EVENT_WINDOW_MINIMIZE: {
1306 guint id = g_signal_lookup("minimize", MAI_TYPE_ATK_OBJECT);
1307 g_signal_emit(atkObj, id, 0);
1308 } break;
1310 case nsIAccessibleEvent::EVENT_WINDOW_RESTORE: {
1311 guint id = g_signal_lookup("restore", MAI_TYPE_ATK_OBJECT);
1312 g_signal_emit(atkObj, id, 0);
1313 } break;
1315 case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE:
1316 g_signal_emit_by_name(atkObj, "load_complete");
1317 // XXX - Handle native dialog accessibles.
1318 if (!accessible->IsRoot() && accessible->HasARIARole() &&
1319 accessible->ARIARole() == roles::DIALOG) {
1320 guint id = g_signal_lookup("activate", MAI_TYPE_ATK_OBJECT);
1321 g_signal_emit(atkObj, id, 0);
1323 break;
1325 case nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD:
1326 g_signal_emit_by_name(atkObj, "reload");
1327 break;
1329 case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED:
1330 g_signal_emit_by_name(atkObj, "load_stopped");
1331 break;
1333 case nsIAccessibleEvent::EVENT_MENUPOPUP_START:
1334 atk_focus_tracker_notify(atkObj); // fire extra focus event
1335 atk_object_notify_state_change(atkObj, ATK_STATE_VISIBLE, true);
1336 atk_object_notify_state_change(atkObj, ATK_STATE_SHOWING, true);
1337 break;
1339 case nsIAccessibleEvent::EVENT_MENUPOPUP_END:
1340 atk_object_notify_state_change(atkObj, ATK_STATE_VISIBLE, false);
1341 atk_object_notify_state_change(atkObj, ATK_STATE_SHOWING, false);
1342 break;
1345 return NS_OK;
1348 void a11y::ProxyEvent(RemoteAccessible* aTarget, uint32_t aEventType) {
1349 AtkObject* wrapper = GetWrapperFor(aTarget);
1351 switch (aEventType) {
1352 case nsIAccessibleEvent::EVENT_FOCUS:
1353 atk_focus_tracker_notify(wrapper);
1354 atk_object_notify_state_change(wrapper, ATK_STATE_FOCUSED, true);
1355 break;
1356 case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE:
1357 g_signal_emit_by_name(wrapper, "load_complete");
1358 break;
1359 case nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD:
1360 g_signal_emit_by_name(wrapper, "reload");
1361 break;
1362 case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED:
1363 g_signal_emit_by_name(wrapper, "load_stopped");
1364 break;
1365 case nsIAccessibleEvent::EVENT_MENUPOPUP_START:
1366 atk_focus_tracker_notify(wrapper); // fire extra focus event
1367 atk_object_notify_state_change(wrapper, ATK_STATE_VISIBLE, true);
1368 atk_object_notify_state_change(wrapper, ATK_STATE_SHOWING, true);
1369 break;
1370 case nsIAccessibleEvent::EVENT_MENUPOPUP_END:
1371 atk_object_notify_state_change(wrapper, ATK_STATE_VISIBLE, false);
1372 atk_object_notify_state_change(wrapper, ATK_STATE_SHOWING, false);
1373 break;
1374 case nsIAccessibleEvent::EVENT_ALERT:
1375 // A hack using state change showing events as alert events.
1376 atk_object_notify_state_change(wrapper, ATK_STATE_SHOWING, true);
1377 break;
1378 case nsIAccessibleEvent::EVENT_VALUE_CHANGE:
1379 g_object_notify((GObject*)wrapper, "accessible-value");
1380 break;
1381 case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED:
1382 g_signal_emit_by_name(wrapper, "text_selection_changed");
1383 break;
1384 case nsIAccessibleEvent::EVENT_SELECTION_WITHIN:
1385 g_signal_emit_by_name(wrapper, "selection_changed");
1386 break;
1387 case nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED:
1388 g_signal_emit_by_name(wrapper, "text-attributes-changed");
1389 break;
1393 void a11y::ProxyStateChangeEvent(RemoteAccessible* aTarget, uint64_t aState,
1394 bool aEnabled) {
1395 MaiAtkObject* atkObj = MAI_ATK_OBJECT(GetWrapperFor(aTarget));
1396 atkObj->FireStateChangeEvent(aState, aEnabled);
1399 void a11y::ProxyCaretMoveEvent(RemoteAccessible* aTarget, int32_t aOffset,
1400 bool aIsSelectionCollapsed) {
1401 AtkObject* wrapper = GetWrapperFor(aTarget);
1402 g_signal_emit_by_name(wrapper, "text_caret_moved", aOffset);
1405 void MaiAtkObject::FireStateChangeEvent(uint64_t aState, bool aEnabled) {
1406 auto state = aState;
1407 int32_t stateIndex = -1;
1408 while (state > 0) {
1409 ++stateIndex;
1410 state >>= 1;
1413 MOZ_ASSERT(
1414 stateIndex >= 0 && stateIndex < static_cast<int32_t>(gAtkStateMapLen),
1415 "No ATK state for internal state was found");
1416 if (stateIndex < 0 || stateIndex >= static_cast<int32_t>(gAtkStateMapLen)) {
1417 return;
1420 if (gAtkStateMap[stateIndex].atkState != kNone) {
1421 MOZ_ASSERT(gAtkStateMap[stateIndex].stateMapEntryType != kNoStateChange,
1422 "State changes should not fired for this state");
1424 if (gAtkStateMap[stateIndex].stateMapEntryType == kMapOpposite) {
1425 aEnabled = !aEnabled;
1428 // Fire state change for first state if there is one to map
1429 atk_object_notify_state_change(&parent, gAtkStateMap[stateIndex].atkState,
1430 aEnabled);
1434 void a11y::ProxyTextChangeEvent(RemoteAccessible* aTarget, const nsString& aStr,
1435 int32_t aStart, uint32_t aLen, bool aIsInsert,
1436 bool aFromUser) {
1437 MaiAtkObject* atkObj = MAI_ATK_OBJECT(GetWrapperFor(aTarget));
1438 atkObj->FireTextChangeEvent(aStr, aStart, aLen, aIsInsert, aFromUser);
1441 #define OLD_TEXT_INSERTED "text_changed::insert"
1442 #define OLD_TEXT_REMOVED "text_changed::delete"
1443 static const char* oldTextChangeStrings[2][2] = {
1444 {OLD_TEXT_REMOVED NON_USER_EVENT, OLD_TEXT_INSERTED NON_USER_EVENT},
1445 {OLD_TEXT_REMOVED, OLD_TEXT_INSERTED}};
1447 #define TEXT_INSERTED "text-insert"
1448 #define TEXT_REMOVED "text-remove"
1449 #define NON_USER_DETAIL "::system"
1450 static const char* textChangedStrings[2][2] = {
1451 {TEXT_REMOVED NON_USER_DETAIL, TEXT_INSERTED NON_USER_DETAIL},
1452 {TEXT_REMOVED, TEXT_INSERTED}};
1454 void MaiAtkObject::FireTextChangeEvent(const nsString& aStr, int32_t aStart,
1455 uint32_t aLen, bool aIsInsert,
1456 bool aFromUser) {
1457 if (gAvailableAtkSignals == eUnknown) {
1458 gAvailableAtkSignals = g_signal_lookup("text-insert", G_OBJECT_TYPE(this))
1459 ? eHaveNewAtkTextSignals
1460 : eNoNewAtkSignals;
1463 if (gAvailableAtkSignals == eNoNewAtkSignals) {
1464 // XXX remove this code and the gHaveNewTextSignals check when we can
1465 // stop supporting old atk since it doesn't really work anyway
1466 // see bug 619002
1467 const char* signal_name = oldTextChangeStrings[aFromUser][aIsInsert];
1468 g_signal_emit_by_name(this, signal_name, aStart, aLen);
1469 } else {
1470 const char* signal_name = textChangedStrings[aFromUser][aIsInsert];
1471 g_signal_emit_by_name(this, signal_name, aStart, aLen,
1472 NS_ConvertUTF16toUTF8(aStr).get());
1476 void a11y::ProxyShowHideEvent(RemoteAccessible* aTarget,
1477 RemoteAccessible* aParent, bool aInsert,
1478 bool aFromUser) {
1479 MaiAtkObject* obj = MAI_ATK_OBJECT(GetWrapperFor(aTarget));
1480 obj->FireAtkShowHideEvent(GetWrapperFor(aParent), aInsert, aFromUser);
1483 #define ADD_EVENT "children_changed::add"
1484 #define HIDE_EVENT "children_changed::remove"
1486 static const char* kMutationStrings[2][2] = {
1487 {HIDE_EVENT NON_USER_EVENT, ADD_EVENT NON_USER_EVENT},
1488 {HIDE_EVENT, ADD_EVENT},
1491 void MaiAtkObject::FireAtkShowHideEvent(AtkObject* aParent, bool aIsAdded,
1492 bool aFromUser) {
1493 int32_t indexInParent = getIndexInParentCB(&this->parent);
1494 const char* signal_name = kMutationStrings[aFromUser][aIsAdded];
1495 g_signal_emit_by_name(aParent, signal_name, indexInParent, this, nullptr);
1498 void a11y::ProxySelectionEvent(RemoteAccessible*, RemoteAccessible* aWidget,
1499 uint32_t) {
1500 MaiAtkObject* obj = MAI_ATK_OBJECT(GetWrapperFor(aWidget));
1501 g_signal_emit_by_name(obj, "selection_changed");
1504 // static
1505 void AccessibleWrap::GetKeyBinding(LocalAccessible* aAccessible,
1506 nsAString& aResult) {
1507 // Return all key bindings including access key and keyboard shortcut.
1509 // Get access key.
1510 nsAutoString keyBindingsStr;
1511 KeyBinding keyBinding = aAccessible->AccessKey();
1512 if (!keyBinding.IsEmpty()) {
1513 keyBinding.AppendToString(keyBindingsStr, KeyBinding::eAtkFormat);
1515 LocalAccessible* parent = aAccessible->LocalParent();
1516 roles::Role role = parent ? parent->Role() : roles::NOTHING;
1517 if (role == roles::PARENT_MENUITEM || role == roles::MENUITEM ||
1518 role == roles::RADIO_MENU_ITEM || role == roles::CHECK_MENU_ITEM) {
1519 // It is submenu, expose keyboard shortcuts from menu hierarchy like
1520 // "s;<Alt>f:s"
1521 nsAutoString keysInHierarchyStr = keyBindingsStr;
1522 do {
1523 KeyBinding parentKeyBinding = parent->AccessKey();
1524 if (!parentKeyBinding.IsEmpty()) {
1525 nsAutoString str;
1526 parentKeyBinding.ToString(str, KeyBinding::eAtkFormat);
1527 str.Append(':');
1529 keysInHierarchyStr.Insert(str, 0);
1531 } while ((parent = parent->LocalParent()) &&
1532 parent->Role() != roles::MENUBAR);
1534 keyBindingsStr.Append(';');
1535 keyBindingsStr.Append(keysInHierarchyStr);
1537 } else {
1538 // No access key, add ';' to point this.
1539 keyBindingsStr.Append(';');
1542 // Get keyboard shortcut.
1543 keyBindingsStr.Append(';');
1544 keyBinding = aAccessible->KeyboardShortcut();
1545 if (!keyBinding.IsEmpty()) {
1546 keyBinding.AppendToString(keyBindingsStr, KeyBinding::eAtkFormat);
1548 aResult = keyBindingsStr;
1551 // static
1552 LocalAccessible* AccessibleWrap::GetColumnHeader(TableAccessible* aAccessible,
1553 int32_t aColIdx) {
1554 if (!aAccessible) {
1555 return nullptr;
1558 LocalAccessible* cell = aAccessible->CellAt(0, aColIdx);
1559 if (!cell) {
1560 return nullptr;
1563 // If the cell at the first row is column header then assume it is column
1564 // header for all rows,
1565 if (cell->Role() == roles::COLUMNHEADER) {
1566 return cell;
1569 // otherwise get column header for the data cell at the first row.
1570 TableCellAccessible* tableCell = cell->AsTableCell();
1571 if (!tableCell) {
1572 return nullptr;
1575 AutoTArray<LocalAccessible*, 10> headerCells;
1576 tableCell->ColHeaderCells(&headerCells);
1577 if (headerCells.IsEmpty()) {
1578 return nullptr;
1581 return headerCells[0];
1584 // static
1585 LocalAccessible* AccessibleWrap::GetRowHeader(TableAccessible* aAccessible,
1586 int32_t aRowIdx) {
1587 if (!aAccessible) {
1588 return nullptr;
1591 LocalAccessible* cell = aAccessible->CellAt(aRowIdx, 0);
1592 if (!cell) {
1593 return nullptr;
1596 // If the cell at the first column is row header then assume it is row
1597 // header for all columns,
1598 if (cell->Role() == roles::ROWHEADER) {
1599 return cell;
1602 // otherwise get row header for the data cell at the first column.
1603 TableCellAccessible* tableCell = cell->AsTableCell();
1604 if (!tableCell) {
1605 return nullptr;
1608 AutoTArray<LocalAccessible*, 10> headerCells;
1609 tableCell->RowHeaderCells(&headerCells);
1610 if (headerCells.IsEmpty()) {
1611 return nullptr;
1614 return headerCells[0];